Declarative partitioning - another take

Started by Amit Langoteover 9 years ago328 messages
#1Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
8 attachment(s)

Hi,

Attached is the latest set of patches to implement declarative
partitioning. There is already a commitfest entry for the same:
https://commitfest.postgresql.org/10/611/

The old discussion is here:
/messages/by-id/55D3093C.5010800@lab.ntt.co.jp/

Attached patches are described below:

0001-Catalog-and-DDL-for-partition-key.patch
0002-psql-and-pg_dump-support-for-partitioned-tables.patch

These patches create the infrastructure and DDL for partitioned
tables.

In addition to a catalog for storing the partition key information, this
adds a new relkind to pg_class.h. PARTITION BY clause is added to CREATE
TABLE. Tables so created are RELKIND_PARTITIONED_REL relations which are
to be special in a number of ways, especially with regard to their
interactions with regular table inheritance features.

PARTITION BY RANGE ({ column_name | ( expression ) } [ opclass ] [, ...])
PARTITION BY LIST ({ column_name | ( expression ) } [ opclass ])

0003-Catalog-and-DDL-for-partition-bounds.patch
0004-psql-and-pg_dump-support-for-partitions.patch

These patches create the infrastructure and DDL for partitions.

Parent-child relationships of a partitioned table and its partitions are
managed behind-the-scenes with inheritance. That means there is a
pg_inherits entry and attributes, constraints, etc. are marked with
inheritance related information appropriately. However this case differs
from a regular inheritance relationship in a number of ways. While the
regular inheritance imposes certain restrictions on what elements a
child's schema is allowed to contain (both at creation time and
after-the-fact), the partitioning related code imposes further
restrictions. For example, while regular inheritance allows a child to
contain its own columns, the partitioning code disallows that. Stuff like
NO INHERIT marking on check constraints, ONLY are ignored by the the
partitioning code.

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

CREATE TABLE partition_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
[ PARTITION BY {RANGE | LIST} ( { column_name | ( expression ) } [ opclass
] [, ...] )

CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
SERVER server_name
[ OPTIONS ( option 'value' [, ... ] ) ]

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

ALTER TABLE parent DETACH PARTITION partition_name

partition_bound_spec is:

FOR VALUES { list_spec | range_spec }

list_spec in FOR VALUES is:

IN ( expression [, ...] )

range_spec in FOR VALUES is:

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

where lower-bound and upper-bound are:

{ ( expression [, ...] ) | UNBOUNDED }

expression can be a string literal, a numeric literal or NULL.

Note that the one can specify PARTITION BY when creating a partition
itself. That is to allow creating multi-level partitioned tables.

0005-Teach-a-few-places-to-use-partition-check-constraint.patch

A partition's bound implicitly constrains the values that are allowed in
the partition key of its rows. The same can be applied to partitions when
inserting data *directly* into them to make sure that only the correct
data is allowed in (if a tuple has been routed from the parent, that
becomes unnecessary). To that end, ExecConstraints() now includes the
above implicit check constraint in the list of constraints it enforces.

Further, to enable constraint based partition exclusion on partitioned
tables, the planner code includes in its list of constraints the above
implicitly defined constraints. This arrangement is temporary however and
will be rendered unnecessary when we implement special data structures and
algorithms within the planner in future versions of this patch to use
partition metadata more effectively for partition exclusion.

Note that the "constraints" referred to above are not some on-disk
structures but those generated internally on-the-fly when requested by a
caller.

0006-Introduce-a-PartitionTreeNode-data-structure.patch
0007-Tuple-routing-for-partitioned-tables.patch

These patches enable routing of tuples inserted into a partitioned table
to one of its leaf partitions. It applies to both COPY FROM and INSERT.
First of these patches introduces a data structure that provides a
convenient means for the tuple routing code to step down a partition tree
one level at a time. The second one modifies copy.c and executor to
implement actual tuple routing. When inserting into a partition, its row
constraints and triggers are applied. Note that the partition's
constraints also include the constraints defined on the parent. This
arrangements means however that the parent's triggers are not currently
applied.

Updates are handled like they are now for inheritance sets, however, if an
update makes a row change partition, an error will be thrown.

0008-Update-DDL-Partitioning-chapter.patch

This patch updates the partitioning section in the DDL chapter to reflect
the new methods made available for creating and managing partitioned table
and its partitions. Especially considering that it is no longer necessary
to define CHECK constraints and triggers/rules manually for constraint
exclusion and tuple routing, respectively.

TODO (in short term):
* Add more regression tests and docs
* Add PartitionOptInfo and use it to perform partition pruning more
effectively (the added infrastructure should also help pairwise joins
patch proposed by Ashutosh Bapat [1]/messages/by-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj=EaDTSA@mail.gmail.com)
* Fix internal representation of list partition bounds to be more efficient

Thanks,
Amit

[1]: /messages/by-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj=EaDTSA@mail.gmail.com
/messages/by-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj=EaDTSA@mail.gmail.com

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables.patchDownload
From b53631393a83a14ebe9687ad6421fcd902a6b541 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_REL relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |   99 +++++++
 doc/src/sgml/ref/create_table.sgml         |   55 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    6 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |    2 +
 src/backend/catalog/heap.c                 |   24 ++-
 src/backend/catalog/objectaddress.c        |   10 +-
 src/backend/catalog/partition.c            |  392 ++++++++++++++++++++++++++++
 src/backend/catalog/pg_depend.c            |    3 +
 src/backend/catalog/pg_partitioned.c       |  171 ++++++++++++
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |    7 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  389 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |    7 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   33 +++
 src/backend/nodes/equalfuncs.c             |   28 ++
 src/backend/nodes/outfuncs.c               |   26 ++
 src/backend/parser/gram.y                  |  110 +++++++--
 src/backend/parser/parse_agg.c             |   11 +
 src/backend/parser/parse_clause.c          |    9 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_relation.c        |    7 +-
 src/backend/parser/parse_utilcmd.c         |   71 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/tcop/utility.c                 |    5 +-
 src/backend/utils/cache/relcache.c         |   14 +-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    8 +-
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/partition.h            |   35 +++
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned.h       |   67 +++++
 src/include/catalog/pg_partitioned_fn.h    |   29 ++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   35 +++
 src/include/parser/kwlist.h                |    1 +
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |    8 +
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 ++++
 src/test/regress/expected/create_table.out |  160 +++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   32 +++
 src/test/regress/sql/create_table.sql      |  135 ++++++++++
 56 files changed, 2048 insertions(+), 46 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/backend/catalog/pg_partitioned.c
 create mode 100644 src/include/catalog/partition.h
 create mode 100644 src/include/catalog/pg_partitioned.h
 create mode 100644 src/include/catalog/pg_partitioned_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccb9b97..b51323a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </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>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4668,6 +4673,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned">
+  <title><structname>pg_partitioned</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned">
+   <primary>pg_partitioned</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned</structname> stores information
+   about the partition key of partitioned tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned</> 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>partedrelid</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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partkey</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..51e6936 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expresion must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns and/or expressions in the partition key and partition
+      rules associated with the partitions.
+     </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.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ba1f3aa..4dc4400 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_REL:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_REL:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..777459d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_type.o storage.o toasting.o pg_partitioned.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_collation.h pg_range.h pg_transform.h pg_partitioned.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..60554de 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
 					 getObjectDescription(object));
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				subflags = 0;	/* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..f5dcc56 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_REL:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_REL ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned tuples.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1985,6 +1996,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	int			keycount;
 	int16	   *attNos;
 	Oid			constrOid;
+	Oid			relid = RelationGetRelid(rel);
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2031,6 +2043,10 @@ 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))
+		is_no_inherit = false;
+
 	/*
 	 * Create the Check Constraint
 	 */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8068b82..6c89b08 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
+		case RELKIND_PARTITIONED_REL:
+			appendStringInfo(buffer, _("partitioned table %s"),
+							 relname);
+			break;
 		case RELKIND_INDEX:
 			appendStringInfo(buffer, _("index %s"),
 							 relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 		case RELKIND_RELATION:
 			appendStringInfoString(buffer, "table");
 			break;
+		case RELKIND_PARTITIONED_REL:
+			appendStringInfoString(buffer, "partitioned table");
+			break;
 		case RELKIND_INDEX:
 			appendStringInfoString(buffer, "index");
 			break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..f00caf2
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,392 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.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_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+	Oid		*typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* 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);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	int2vector	   *partattrs;
+	oidvector	   *opclass;
+	KeyTypeCollInfo *tcinfo;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+	/* Allocate in the supposedly short-lived working context */
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/* Open the catalog for its tuple descriptor */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	partattrs = (int2vector *) DatumGetPointer(datum);
+
+	datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+
+	if (!isnull)
+	{
+		char   *exprsString;
+		Node   *exprs;
+
+		exprsString = TextDatumGetCString(datum);
+		exprs = stringToNode(exprsString);
+		pfree(exprsString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) exprs);
+		key->partexprs = (List *) exprs;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+	tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+	tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/*
+	 * Copy partattrs. Further, determine and store the opfamily, opcintype,
+	 * and btree support function per attribute.
+	 */
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		HeapTuple		tuple;
+		AttrNumber		attno;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		key->partattrs[i] = attno = partattrs->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+			tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+			tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+		get_typlenbyvalalign(tcinfo->typid[i],
+							 &tcinfo->typlen[i],
+							 &tcinfo->typbyval[i],
+							 &tcinfo->typalign[i]);
+
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+
+	FreePartitionKey(key);
+}
+
+/*
+ * FreePartitionKey
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+	if (key == NULL)
+		return;
+
+	pfree(key->partattrs);
+	pfree(key->partopfamily);
+	pfree(key->partopcintype);
+	pfree(key->partsupfunc);
+	if (key->partexprs)
+		pfree(key->partexprs);
+	free_key_type_coll_info(key->tcinfo);
+	pfree(key);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->tcinfo->typid[col];
+}
+
+extern
+int32 get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->tcinfo->typmod[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+
+	newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *)
+							palloc(newkey->partnatts * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs,
+							newkey->partnatts * sizeof(AttrNumber));
+
+	newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *)
+							palloc(newkey->partnatts * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+	newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+											 fromkey->tcinfo);
+
+	return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+	KeyTypeCollInfo   *result = (KeyTypeCollInfo *)
+								palloc0(sizeof(KeyTypeCollInfo));
+
+	result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+	result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+	memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+	result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+	memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+	result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+	result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+	result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+	return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+	Assert(tcinfo != NULL);
+
+	pfree(tcinfo->typid);
+	pfree(tcinfo->typmod);
+	pfree(tcinfo->typlen);
+	pfree(tcinfo->typbyval);
+	pfree(tcinfo->typalign);
+	pfree(tcinfo->typcoll);
+	pfree(tcinfo);
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
 
+	if (behavior == DEPENDENCY_IGNORE)
+		return;					/* nothing to do */
+
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
 
diff --git a/src/backend/catalog/pg_partitioned.c b/src/backend/catalog/pg_partitioned.c
new file mode 100644
index 0000000..ab967d5
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned.c
@@ -0,0 +1,171 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.c
+ *	  routines to support manipulation of the pg_partitioned 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_partitioned.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partitioned_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprsDatum;
+	Relation	pg_partitioned;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned];
+	bool		nulls[Natts_pg_partitioned];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/*
+	 * Copy the partition key, opclass info into arrays (should we
+	 * make the caller pass them like this to start with?)
+	 */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprsString;
+
+		exprsString = nodeToString(partexprs);
+		partexprsDatum = CStringGetTextDatum(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprsDatum = (Datum) 0;
+
+	pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprsDatum)
+		nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+	simple_heap_insert(pg_partitioned, tuple);
+
+	/* Update the indexes on pg_partitioned */
+	CatalogUpdateIndexes(pg_partitioned, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_IGNORE);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+	return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9ac7122..678e079 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1323,6 +1324,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f45b330..53b2226 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bc2e4af..cadc9e4 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..e070a1a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
 	bool		concurrent;
 };
 
+/* for find_attr_reference_walker */
+typedef struct
+{
+	AttrNumber	attnum;
+} find_attr_reference_context;
+
 /* Alter table target-type flags for ATSimplePermissions */
 #define		ATT_TABLE				0x0001
 #define		ATT_VIEW				0x0002
@@ -433,6 +441,13 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+								AttrNumber *partattrs,
+								List **partexprs,
+								Oid *partoplass);
 
 
 /* ----------------------------------------------------------------
@@ -596,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_REL));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +713,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partby)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		stmt->partby = transformPartitionBy(rel, stmt->partby);
+		ComputePartitionAttrs(relationId, stmt->partby->partParams,
+							  partattrs, &partexprs, partopclass);
+
+		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.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +995,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both normal and partitioned tables are dropped using DROP TABLE.
+	 * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+	 * relkind for OBJECT_TABLE relations. A mismatch below may have
+	 * to do with that. So, check.
+	 */
+	if (classform->relkind != relkind &&
+				(relkind == RELKIND_RELATION &&
+					classform->relkind != RELKIND_PARTITIONED_REL))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1341,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,8 +1570,16 @@ 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)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2162,6 +2219,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_REL &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4349,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4586,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5477,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5752,77 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var		   *variable = (Var *) node;
+		AttrNumber	attnum = variable->varattno;
+
+		if (attnum == context->attnum)
+			return true;
+	}
+
+	return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+	{
+		*is_expr = false;
+		return false;
+	}
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber partatt = get_partition_col_attnum(key, i);
+
+		if(partatt != 0)
+		{
+			*is_expr = false;
+			if (attnum == partatt)
+				return true;
+		}
+		else
+		{
+			find_attr_reference_context context;
+
+			*is_expr = true;
+			context.attnum = attnum;
+			if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+				return true;
+
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5837,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5882,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6413,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8013,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7891,6 +8044,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8072,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_REL)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8933,6 +9100,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9563,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9986,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_REL) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10186,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10067,6 +10242,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(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.
@@ -11445,6 +11626,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12076,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12230,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_REL &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12242,195 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * transformPartitionBy
+ * 		Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+	PartitionBy	   *partby;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	partby = (PartitionBy *) makeNode(PartitionBy);
+
+	partby->strategy = partitionby->strategy;
+	partby->location = partitionby->location;
+	partby->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *column;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(column, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(column);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+
+			/*
+			 * transformExpr() should have already rejected subqueries,
+			 * aggregates, and window functions, based on the EXPR_KIND_ for
+			 * a partition key expression.
+			 *
+			 * Also reject expressions returning sets; this is for consistency
+			 * DefineRelation() will make more checks.
+			 */
+			if (expression_returns_set(pelem->expr))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key expression cannot return a set"),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		partby->partParams = lappend(partby->partParams, pelem);
+	}
+
+	return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ *		Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(relid, pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			if (IsA(expr, CollateExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot use COLLATE in partition key expression")));
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				partattrs[attn] = 0; /* marks expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * transformExpr() should have already rejected subqueries,
+				 * aggregates, and window functions, based on the EXPR_KIND_
+				 * for a partition key expression.
+				 */
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use a constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+			}
+		}
+
+		/*
+		 * Identify the opclass to use. At the moment, we use "btree" operators
+		 * that seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify an existing btree operator class or define one for the type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 99a659a..6da6f32 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0563e63..804e56f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1315,6 +1315,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..3c73e81 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..8e2da9c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_REL ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3244c76..700d7be 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2999,6 +2999,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4154,6 +4155,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+	PartitionBy *newnode = makeNode(PartitionBy);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5065,6 +5092,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionBy:
+			retval = _copyPartitionBy(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..9554c2f 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(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionBy:
+			retval = _equalPartitionBy(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 acaf4ea..4d5e37e 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(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3266,6 +3267,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3847,6 +3868,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionBy:
+				_outPartitionBy(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0cae446..e912a2d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partby>		PartitionBy OptPartitionBy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partby = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partby = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partby = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partby = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13846,6 +13919,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..296ab89 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,8 +21,10 @@
 #include "access/tsmapi.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "nodes/makefuncs.h"
@@ -200,6 +202,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 										relation->alias, inh, false);
 	pstate->p_target_rangetblentry = rte;
 
+	/*
+	 * Override specified inheritance option, if relation is a partitioned
+	 * table and it's a target of INSERT.
+	 */
+	if (alsoSource)
+		rte->inh |= relid_is_partitioned(rte->relid);
+
 	/* assume new rte is at end */
 	rtindex = list_length(pstate->p_rtable);
 	Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index cead212..a97f727 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1748,6 +1748,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3297,6 +3300,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 1e3ecbc..76bb427 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,8 @@
 #include "access/sysattr.h"
 #include "catalog/heap.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "nodes/makefuncs.h"
@@ -1216,9 +1218,12 @@ addRangeTableEntry(ParseState *pstate,
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
+	 *
+	 * Note: we override the specified inheritance option, if relation is a
+	 * partitioned table.
 	 */
 	rte->lateral = false;
-	rte->inh = inh;
+	rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
 	rte->inFromCl = inFromCl;
 
 	rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e98fad0..be21a8e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,12 +33,14 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_constraint_fn.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_type.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -87,6 +89,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +232,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partby != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +251,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partby)
+	{
+		int		partnatts = list_length(stmt->partby->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -582,6 +609,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -591,6 +624,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +647,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -673,6 +718,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -683,6 +734,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -693,6 +750,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -707,6 +770,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -759,6 +828,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2517,6 +2587,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = relid_is_partitioned(relid);
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..eb71efc 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partby != NULL
+													? RELKIND_PARTITIONED_REL
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..3787441 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_REL:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -1053,6 +1055,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		RelationBuildPartitionKey(relation);
+	else
+		relation->rd_partkey = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2050,6 +2058,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	FreePartitionKey(relation->rd_partkey);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2991,7 +3000,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_REL ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5045,6 +5056,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_partedrelid,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
  * created only during initdb.  The fields for the dependent object
  * contain zeroes.
  *
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question.  However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
  * Other dependency flavors may be needed in future.
  */
 
@@ -77,7 +82,8 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
+	DEPENDENCY_IGNORE = 'g'
 } DependencyType;
 
 /*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* 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
new file mode 100644
index 0000000..acff099
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+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);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..57c7ca4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_REL 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ *	  definition of the system "partitioned" relation (pg_partitioned)
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partedrelid;	/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprs;		/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ *      Form_pg_partitioned corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ *      compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned				6
+#define Anum_pg_partitioned_partedrelid		1
+#define Anum_pg_partitioned_partstrat		2
+#define Anum_pg_partitioned_partnatts		3
+#define Anum_pg_partitioned_partattrs		4
+#define Anum_pg_partitioned_partclass		5
+#define Anum_pg_partitioned_partexprs		6
+
+#endif   /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_partitioned_fn.h b/src/include/catalog/pg_partitioned_fn.h
new file mode 100644
index 0000000..3e97740
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_FN_H
+#define PG_PARTITIONED_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif   /* PG_PARTITIONED_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..9fe31ce 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..c316af6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionBy,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..2b77579 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionBy
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionBy;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1751,6 +1785,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..e05d122 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,8 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +534,12 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..cb105af 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+DROP TABLE partitioned;
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+DROP TABLE no_drop_or_alter_partcol, no_drop_or_alter_partexpr;
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+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
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..fb7a1b3 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,163 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR:  cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  partitioned table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from partitioned table "no_inh_parted"
+DROP TABLE no_inh_parted;
+-- NO INHERIT ignored on constraints
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
+ connoinherit 
+--------------
+ f
+(1 row)
+
+DROP TABLE no_inh_con_parted;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 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_partitioned|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..f11f856 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,35 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+DROP TABLE partitioned;
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+DROP TABLE no_drop_or_alter_partcol, no_drop_or_alter_partexpr;
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) 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;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..cb17517 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,138 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- NO INHERIT ignored on constraints
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
+DROP TABLE no_inh_con_parted;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables.patchDownload
From 65965857175167028ed90262ce6deb090c93e57f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 +++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relationId = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+	Form_pg_partitioned		form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+	form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relId), relId);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	appendStringInfo(&buf, "PARTITION BY ");
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relId, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relId, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											prettyFlags, 0);
+
+			/* Need parens if it's not a bare function call */
+			if (partkey && IsA(partkey, FuncExpr) &&
+				((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+				appendStringInfoString(&buf, str);
+			else
+				appendStringInfo(&buf, "(%s)", str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15436,6 +15450,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+				appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15496,6 +15513,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15514,7 +15532,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_REL)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,7 @@ permissionsList(const char *pattern)
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
 					  gettext_noop("Schema"),
@@ -865,6 +866,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("partitioned table"),
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "c.relacl");
@@ -911,7 +913,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1555,8 +1557,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1621,6 +1623,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1634,8 +1644,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1655,12 +1665,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1760,7 +1770,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1768,14 +1778,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1914,7 +1943,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2473,7 +2502,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2684,7 +2713,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3018,6 +3047,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 's' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3029,6 +3059,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("sequence"),
 					  gettext_noop("special"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("partitioned table"),
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions.patchDownload
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

0004-psql-and-pg_dump-support-for-partitions.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions.patchDownload
From 12b0f657def1c6ed439959f08ecdfec4de53dc15 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   46 ++++++++++++-
 src/test/regress/expected/create_table.out |   23 +++++++
 src/test/regress/sql/create_table.sql      |    6 ++
 7 files changed, 340 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4691b5b..b29fec1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8350,6 +8350,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a127672..8e4231e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6124,6 +6124,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT partrelid, inhparent AS partparent, "
+						 "		 pg_get_expr(partbound, partrelid) AS partbound "
+						 "FROM pg_partition, pg_inherits "
+						 "WHERE partrelid = inhrelid");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15301,6 +15358,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15329,8 +15397,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15359,7 +15430,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15424,15 +15496,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15602,6 +15681,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3ba8a90..32b84ca 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1786,6 +1786,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+			 " FROM pg_catalog.pg_partition p"
+			 " JOIN pg_catalog.pg_inherits i"
+			 " ON p.partrelid = i.inhrelid"
+			 " WHERE p.partrelid = '%s';", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2547,8 +2575,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2577,9 +2609,15 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
+		/* print child tables (exclude, if parent is a partitioned table) */
 		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 9295093..31bd1d8 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -609,6 +609,29 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+   Partitioned table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: PARTITION BY RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1e14c4a..d920abb 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -545,6 +545,12 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-constraint.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-constraint.patchDownload
From f2f6f8a9e4c66f94bf4f6b1610bccfec9d5c5108 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check constraint expressions.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 ++++++++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 ++++++++
 src/include/nodes/execnodes.h          |    4 ++
 src/test/regress/expected/insert.out   |   76 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/update.out   |   27 +++++++++++
 src/test/regress/sql/insert.sql        |   56 +++++++++++++++++++++++
 src/test/regress/sql/update.sql        |   21 +++++++++
 9 files changed, 280 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53b2226..76803a5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3c73e81..489ff02 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8e2da9c..3ed321e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..249a5aa 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionCheckQual(relation);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..31ca9ed 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..589efb8 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to partitioned table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure.patchDownload
From e47a4f83a69948cfdaba26914079009696cbeccd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  231 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    6 +
 2 files changed, 237 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 109d391..70bfd88 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -110,6 +110,61 @@ typedef struct PartitionInfoData
 	PartitionRangeInfo	   *range;		/* range partition info */
 } PartitionInfoData;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo					PartitionKey executor state
+ *
+ *	pdesc					Info about immediate partitions (see
+ *							PartitionDescData)
+ *
+ *	index					If a partition ourselves, index in the parent's
+ *							partition array
+ *
+ *	num_leaf_partitions		Number of leaf partitions in the partition
+ *							tree rooted at this node
+ *
+ *	offset					0-based index of the first leaf partition
+ *							in the partition tree rooted at this node
+ *
+ *	downlink				Link to our leftmost child node (ie, corresponding
+ *							to first of our partitions that is itself
+ *							partitioned)
+ *
+ *	next					Link to the right sibling node on a given level
+ *							(ie, corresponding to the next partition on the same
+ *							level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
@@ -153,6 +208,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionCheckQual() */
 static List *generate_partition_check_qual(Relation rel);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -815,6 +874,81 @@ RelationGetPartitionCheckQual(Relation rel)
 	return generate_partition_check_qual(rel);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = get_partitions(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+
+		if (relid_is_partitioned(myoid))
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+
+		result = lappend_oid(result, myoid);
+	}
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1556,6 +1690,103 @@ generate_partition_check_qual(Relation rel)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->parts[i]->oid;
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		/* Skip a leaf partition */
+		if (!relid_is_partitioned(relid))
+			continue;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index db59c9a..89c22dc 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -30,6 +30,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -56,4 +57,9 @@ 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);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables.patchDownload
From f34dd88452ec38035d1e77ee1073b9c9c2503966 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  339 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  198 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   46 ++++-
 src/backend/executor/nodeModifyTable.c  |  116 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 884 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 70bfd88..569d47b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -212,6 +212,18 @@ static List *generate_partition_check_qual(Relation rel);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -240,6 +252,10 @@ static int32 partition_range_tuple_cmp(PartitionKey key,
 						   Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key,
 							PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple);
 
 /*
  * Partition key related functions
@@ -1706,7 +1722,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1787,6 +1803,261 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = copy_partition_key(rel->rd_partkey);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (!relid_is_partitioned(ptnode->pdesc->parts[index]->oid))
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			return node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			return ptnode->offset + index;
+
+	}
+
+	/*
+	 * Recurse by locating the index'th partition's node and passing it down
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	int			i;
+
+	Assert(pdesc->nparts > 0);
+
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		int		j;
+
+		for (j = 0; j < pdesc->parts[i]->list->nvalues; j++)
+		{
+			if (isnull)
+			{
+				if (pdesc->parts[i]->list->nulls[j])
+					return i;
+				continue;
+			}
+
+			if (!pdesc->parts[i]->list->nulls[j] &&
+				partition_list_values_equal(key,
+											pdesc->parts[i]->list->values[j],
+											value))
+				return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+
+	return range_partition_bsearch(key, pdesc, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -2105,3 +2376,69 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * range_partition_bsearch
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = pdesc->nparts - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (pdesc->parts[idx]->range->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, pdesc->parts[idx]->range->upper))
+		{
+			if (pdesc->parts[idx]->range->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 76803a5..3741d9f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1370,87 @@ BeginCopy(bool is_from,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1659,6 +1746,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1672,6 +1761,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2216,6 +2322,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2236,7 +2343,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2344,6 +2452,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2480,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2392,10 +2502,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2447,6 +2593,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2467,7 +2638,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2497,7 +2677,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2698,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2530,7 +2717,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 361b5ea..90d9bc9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1253,6 +1253,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 489ff02..a411ba3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionCheckQual(resultRelationDesc);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3ed321e..94b757b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,72 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2056,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 54d601f..9c60ed5 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6151,6 +6152,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (!relid_is_partitioned(rte->relid))
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 249a5aa..b623209 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1703,3 +1703,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..e4a4dd1 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -797,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 89c22dc..c3180b7 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/relcache.h"
 
@@ -62,4 +64,9 @@ extern List *RelationGetPartitionCheckQual(Relation rel);
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 31ca9ed..8943172 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 589efb8..760ec87 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_nulls       |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to partitioned table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel.patchDownload
From f0955db793a105c326f598281eb45868be8e0045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 954c3a9..4196695 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#2Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#1)
Re: Declarative partitioning - another take

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached is the latest set of patches to implement declarative
partitioning.

Cool. I would encourage you to give some thought to what is the least
committable subset of these patches, and think about whether it can be
reorganized to make that smaller. Based on the descriptions, it
sounds to me like the least committable subset is probably all of 0001
- 0005 as one giant commit, and that's a lot of code. Maybe there's
no real way to come up with something more compact than that, but it's
worth some thought.

0001-Catalog-and-DDL-for-partition-key.patch

+ <entry><link
linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>

pg_partitioned seems like a slightly strange choice of name. We have
no other catalog tables whose names end in "ed", so the use of a past
participle here is novel. More generally, this patch seems like it's
suffering a bit of terminological confusion: while the catalog table
is called pg_partitioned, the relkind is RELKIND_PARTITION_REL. And
get_relation_by_qualified_name() thinks that RELKIND_PARTITION_REL is
a "table", but getRelationDescription thinks it's a "partitioned
table". I think we need to get all these bits and pieces on the same
page, or have some kind of consistent way of deciding what to do in
each case. Maybe pg_partitioned_table, RELKIND_PARTITIONED_TABLE,
etc. for the internal stuff, but just "table" in user-facing messages.

Alternatively, I wonder if we should switch to calling this a
"partition root" rather than a "partitioned table". It's not the
whole table, just the root. And doesn't contain data - in fact it
probably shouldn't even have storage - so calling it a "table" might
be confusing. But if we're using CREATE TABLE to create it in the
ifrst place, then calling it something other than a table later on is
also confusing. Hmm.

+ <entry><structfield>partexprs</structfield></entry>

There's a certain symmetry between this and what we do for indexes,
but I'm wondering whether there's a use case for partitioning a table
by an expression rather than a column value. I suppose if you've
already done the work, there's no harm in supporting it.

+[ PARTITION BY {RANGE | LIST} ( { <replaceable
class="parameter">column_name</replaceable> | ( <replaceable
class="parameter">expression</replaceable> ) } [ <replaceable
class="parameter">opclass</replaceable> ] [, ...] )

The spacing isn't right here. Should say { RANGE | LIST } rather than
{RANGE|LIST}. Similarly elsewhere.

+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.

Why do we support range partitioning with multiple columns, but list
partitioning only with a single column?

+ The type of a key column or an expresion must have an associated

Typo.

+     <para>
+      Currently, there are following limitations on definition of partitioned
+      tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+      FOREIGN KEY constraints.
+     </para>

But I presume you can define these constraints on the partitions;
that's probably worth mentioning. I'd say something like this
"Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or
FOREIGN KEY constraints; however, you can define these constraints on
individual data partitions."

+    if (partexprs)
+        recordDependencyOnSingleRelExpr(&myself,
+                                        (Node *) partexprs,
+                                        RelationGetRelid(rel),
+                                        DEPENDENCY_NORMAL,
+                                        DEPENDENCY_IGNORE);

I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
here. Instead, you could just add an additional Boolean argument to
recordDependencyOnSingleRelExpr. That seems less likely to create
bugs in unrelated portions of the code.

+    /*
+     * Override specified inheritance option, if relation is a partitioned
+     * table and it's a target of INSERT.
+     */
+    if (alsoSource)
+        rte->inh |= relid_is_partitioned(rte->relid);

This seems extremely unlikely to be the right way to do this. We're
just doing parse analysis here, so depending on properties of the
table that are (or might be made to be) changeable is not good. It's
also an extra catalog lookup whether the table is partitioned or not
(and whether rte->inh is already true or not). Furthermore, it seems
to go against the comment explaining what rte->inh is supposed to
mean, which says:

* inh is TRUE for relation references that should be expanded to include
* inheritance children, if the rel has any. This *must* be FALSE for
* RTEs other than RTE_RELATION entries.

I am not sure what problem you're trying to solve here, but I suspect
this needs to be ripped out altogether or handled later, during
planning. Similarly for the related change in addRangeTableEntry.

+    {PartitionedRelationId,        /* PARTEDRELID */
+        PartitionedRelidIndexId,
+        1,
+        {
+            Anum_pg_partitioned_partedrelid,
+            0,
+            0,
+            0
+        },
+        128

I'd probably cut the initial size of this down a bit. Odds are good
that not all of the tables you access will be partitioned. Of course
that assumes we'll avoid probing this syscache for non-partitioned
tables, but I think that's pretty important.

+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                     errmsg("cannot alter column named in partition key")));
+        else
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                     errmsg("cannot alter column referenced in
partition key expression")));

Maybe "cannot alter type of column named in partition key"? And
similarly for the other one.

+/*
+ * transformPartitionBy
+ *         Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)

Shouldn't this be in src/backend/parser/parse_*.c instead of tablecmds.c?

Most of this (0001) looks pretty reasonable. I'm sure that there is
more that needs fixing than what I've mentioned above, which is just
what I saw on an initial read-through, but overall I like the
approach.

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

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

#3Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#2)
Re: Declarative partitioning - another take

Thanks a lot for taking a look at this.

On 2016/08/11 3:22, Robert Haas wrote:

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached is the latest set of patches to implement declarative
partitioning.

Cool. I would encourage you to give some thought to what is the least
committable subset of these patches, and think about whether it can be
reorganized to make that smaller. Based on the descriptions, it
sounds to me like the least committable subset is probably all of 0001
- 0005 as one giant commit, and that's a lot of code. Maybe there's
no real way to come up with something more compact than that, but it's
worth some thought.

I will consider this and try to come up with a minimal patch set covering
what is now 0001 - 0005.

0001-Catalog-and-DDL-for-partition-key.patch

+ <entry><link
linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>

pg_partitioned seems like a slightly strange choice of name. We have
no other catalog tables whose names end in "ed", so the use of a past
participle here is novel. More generally, this patch seems like it's
suffering a bit of terminological confusion: while the catalog table
is called pg_partitioned, the relkind is RELKIND_PARTITION_REL. And
get_relation_by_qualified_name() thinks that RELKIND_PARTITION_REL is
a "table", but getRelationDescription thinks it's a "partitioned
table". I think we need to get all these bits and pieces on the same
page, or have some kind of consistent way of deciding what to do in
each case. Maybe pg_partitioned_table, RELKIND_PARTITIONED_TABLE,
etc. for the internal stuff, but just "table" in user-facing messages.

Name pg_partitioned does sound a little strange now that you mention it.

I agree to make the terminology more consistent and to that end, your
suggestion to keep the user-facing term "table" and using
pg_partitioned_table and RELKIND_PARTITIONED_TABLE for internals sounds good.

Alternatively, I wonder if we should switch to calling this a
"partition root" rather than a "partitioned table". It's not the
whole table, just the root. And doesn't contain data - in fact it
probably shouldn't even have storage - so calling it a "table" might
be confusing. But if we're using CREATE TABLE to create it in the
ifrst place, then calling it something other than a table later on is
also confusing. Hmm.

I think it makes sense to keep calling it a table because it has all the
logical properties of a table even though it will differ from a regular
table on the basis of physical implementation details such as that it does
not own physical storage. Am I missing something?

+ <entry><structfield>partexprs</structfield></entry>

There's a certain symmetry between this and what we do for indexes,
but I'm wondering whether there's a use case for partitioning a table
by an expression rather than a column value. I suppose if you've
already done the work, there's no harm in supporting it.

Yeah, it's not a whole lot of code to manage expressions alongside simple
column references.

+[ PARTITION BY {RANGE | LIST} ( { <replaceable
class="parameter">column_name</replaceable> | ( <replaceable
class="parameter">expression</replaceable> ) } [ <replaceable
class="parameter">opclass</replaceable> ] [, ...] )

The spacing isn't right here. Should say { RANGE | LIST } rather than
{RANGE|LIST}. Similarly elsewhere.

Will fix.

+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.

Why do we support range partitioning with multiple columns, but list
partitioning only with a single column?

This is mostly because I didn't find any other database supporting it and
hence thought maybe there is not much use for it.

On the implementation side, I think it makes sense to think of ordering of
tuples (for a multi-column range key), where we compare a new tuple's
partition key columns one-by-one until we find the column such that its
value != the value of corresponding column in the range upper bound of a
partition (usually but not necessarily the last column of the key). Once
we have determined which side of the bound the value was, we then perform
the same process with either the lower bound of the same partition or
upper bound of some partition in the other half as determined by binary
search logic.

Conversely, when determining the constraint on rows a range partition
contains, we emit a (keycol = lowerval) expression for all columns i =
0..j-1 where range.lowerval[i] = range.upperval[i] and then emit (keycol >
/ >= range.lowerval[j] and keycol < / <= upperval[j]) for the first column
j where lowerval[j] < upperval[j] and stop at that column (any further
columns are irrelevant).

When considering the same for list partitioning where we consider set-
membership of values (based on equality of a new tuple's value for the
column and a partition's list of values), implementing multi-column logic
does not seem as straightforward. Also, it might make the optimizations
we recently discussed slightly more complicated to implement. Although I
may be missing something.

+ The type of a key column or an expresion must have an associated

Typo.

Will fix.

+     <para>
+      Currently, there are following limitations on definition of partitioned
+      tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+      FOREIGN KEY constraints.
+     </para>

But I presume you can define these constraints on the partitions;
that's probably worth mentioning. I'd say something like this
"Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or
FOREIGN KEY constraints; however, you can define these constraints on
individual data partitions."

Okay, will mention that.

+    if (partexprs)
+        recordDependencyOnSingleRelExpr(&myself,
+                                        (Node *) partexprs,
+                                        RelationGetRelid(rel),
+                                        DEPENDENCY_NORMAL,
+                                        DEPENDENCY_IGNORE);

I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
here. Instead, you could just add an additional Boolean argument to
recordDependencyOnSingleRelExpr. That seems less likely to create
bugs in unrelated portions of the code.

I did consider a Boolean argument instead of a new DependencyType value,
however it felt a bit strange to pass a valid value for the fifth argument
(self_behavior) and then ask using a separate parameter that it (a
self-dependency) is to be ignored. By the way, no pg_depend entry is
created on such a call, so the effect of the new type's usage seems
localized to me. Thoughts?

+    /*
+     * Override specified inheritance option, if relation is a partitioned
+     * table and it's a target of INSERT.
+     */
+    if (alsoSource)
+        rte->inh |= relid_is_partitioned(rte->relid);

This seems extremely unlikely to be the right way to do this. We're
just doing parse analysis here, so depending on properties of the
table that are (or might be made to be) changeable is not good. It's
also an extra catalog lookup whether the table is partitioned or not
(and whether rte->inh is already true or not). Furthermore, it seems
to go against the comment explaining what rte->inh is supposed to
mean, which says:

* inh is TRUE for relation references that should be expanded to include
* inheritance children, if the rel has any. This *must* be FALSE for
* RTEs other than RTE_RELATION entries.

I am not sure what problem you're trying to solve here, but I suspect
this needs to be ripped out altogether or handled later, during
planning. Similarly for the related change in addRangeTableEntry.

Okay, I will see how this could rather be done within the planner. My
intention here is to keep the code churn low by not modifying a lot of
places in the code where rte->inh is checked to turn inheritance on or
off. I agree though that it's a bit ugly and perhaps wrong.

+    {PartitionedRelationId,        /* PARTEDRELID */
+        PartitionedRelidIndexId,
+        1,
+        {
+            Anum_pg_partitioned_partedrelid,
+            0,
+            0,
+            0
+        },
+        128

I'd probably cut the initial size of this down a bit. Odds are good
that not all of the tables you access will be partitioned. Of course
that assumes we'll avoid probing this syscache for non-partitioned
tables, but I think that's pretty important.

Okay, I will turn that down to 64 maybe.

+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                     errmsg("cannot alter column named in partition key")));
+        else
+            ereport(ERROR,
+                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                     errmsg("cannot alter column referenced in
partition key expression")));

Maybe "cannot alter type of column named in partition key"? And
similarly for the other one.

Will fix.

+/*
+ * transformPartitionBy
+ *         Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)

Shouldn't this be in src/backend/parser/parse_*.c instead of tablecmds.c?

It can be performed only after the new relation is opened which can be
only after we have called heap_create_with_catalog() which is in
tablecmds.c: DefineRelation(). I think that's because we call
transformExpr() on partition expressions which expects to be able to see
the table's columns. Other callers such as transformIndexStmt and
transformAlterTableStmt can do transformations within parse_utilcmds.c
because they can open the relation there.

Most of this (0001) looks pretty reasonable. I'm sure that there is
more that needs fixing than what I've mentioned above, which is just
what I saw on an initial read-through, but overall I like the
approach.

Okay, thanks. I will post the updated patches soon.

Thanks,
Amit

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

#4Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#3)
Re: Declarative partitioning - another take

I think it makes sense to keep calling it a table because it has all the
logical properties of a table even though it will differ from a regular
table on the basis of physical implementation details such as that it does
not own physical storage. Am I missing something?

+ <entry><structfield>partexprs</structfield></entry>

There's a certain symmetry between this and what we do for indexes,
but I'm wondering whether there's a use case for partitioning a table
by an expression rather than a column value. I suppose if you've
already done the work, there's no harm in supporting it.

Yeah, it's not a whole lot of code to manage expressions alongside simple
column references.

Users who would like to partition their tables by "age" will partition
those by the month or year extracted out of a date column e.g. order_date.
They will find it convenient to use an expression (extract(month from
date)) as a partition key, instead of storing month or year as a separate
column.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#5Robert Eckhardt
reckhardt@pivotal.io
In reply to: Ashutosh Bapat (#4)
Re: Declarative partitioning - another take

On Tue, Aug 16, 2016 at 2:30 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

I think it makes sense to keep calling it a table because it has all the
logical properties of a table even though it will differ from a regular
table on the basis of physical implementation details such as that it does
not own physical storage. Am I missing something?

+ <entry><structfield>partexprs</structfield></entry>

There's a certain symmetry between this and what we do for indexes,
but I'm wondering whether there's a use case for partitioning a table
by an expression rather than a column value. I suppose if you've
already done the work, there's no harm in supporting it.

Yeah, it's not a whole lot of code to manage expressions alongside simple
column references.

Users who would like to partition their tables by "age" will partition
those by the month or year extracted out of a date column e.g. order_date.
They will find it convenient to use an expression (extract(month from
date)) as a partition key, instead of storing month or year as a separate
column.

In GPDB we have partitioning. It is almost always by date and then often
the partitions are for different sizes, i.e. by day for 30 days then by
month for 3 years then by year. What we also support, but isn't super
performant, is sub-partitioning.

This is where some on the newer indexing strategies is interesting to me. I
see them as synergistic not redundant.

Show quoted text

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#6Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#1)
Re: Declarative partitioning - another take

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

0002-psql-and-pg_dump-support-for-partitioned-tables.patch

+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')

Version check is redundant, right?

+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers
+--------+---------+-----------
+ a      | integer |
+ b      | integer |
+Partition Key: PARTITION BY RANGE (((a + b)))

I understand that it's probably difficult not to end up with two sets
of parentheses here, but can we avoid ending up with three sets?

Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
pg_dump can add that part back. Then this could say:

Partition Key: RANGE ((a + b))

...which seems a good deal more natural than what you have now.

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

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

#7Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#1)
Re: Declarative partitioning - another take

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

0003-Catalog-and-DDL-for-partition-bounds.patch

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

Hmm, I don't think I like this. Why should it be necessary to detach
a partition before dropping it? That seems like an unnecessary step.

[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
IMMEDIATE ]
+
</synopsis>

Unnecessary hunk.

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

Sentence fragment.

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

This is another restriction I don't understand. Why can't I attach a
partitioned table?

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

Ugh, why? I think this should work exactly the same way for
partitioned tables that it does for any other inheritance hierarchy.
Sure, you'll get no rows, but who cares?

+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES START ('2016-07-01') END
('2016-08-01');
+    SERVER server_07;

Extra semicolon?

+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be

I think experience suggests that this is a good restriction, but then
why does the syntax synopsis indicate that PARTITION BY can be
specified along with column definitions? Similarly for CREATE FOREIGN
TABLE.

+      When specifying for a table being created as partition, one needs to
+      use column names from the parent table as part of the key.

This is not very clear.

-       /* 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;

It might be right to disallow NO INHERIT in this case, but I don't
think it can be right to just silently ignore it.

+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.

This seems unlikely to be safe, unless I'm missing something.

+       form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+       systable_endscan(scan);
+       heap_close(catalogRelation, AccessShareLock);
+
+       return form->inhparent;

This is unsafe. After systable_endscan, it is no longer OK to access
form->inhparent.

Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.

There should probably be a note in the function header comment that it
is unsafe to use this for an inheritance child that is not a
partition, because there could be more than one parent in that case.
Or maybe the whole idea of this function just isn't very sound...

+static List *
+get_partitions(Oid relid, int lockmode)
+{
+       return find_inheritance_children(relid, lockmode);
+}

What's the point? If we're going to have a wrapper here at all, then
shouldn't it have a name that matches the existing convention - e.g.
find_partitions() or find_child_partitions()? But I think you might
as well just use find_inheritance_children() directly.

+                * Happens when we have created the pg_inherits entry
but not the
+                * pg_partition entry yet.

Why do we ever allow the flow of control to reach this point while we
are in such an intermediate state?

+free_partition_info(PartitionInfoData **p, int num)

Seems very error-prone. Isn't this why MemoryContextReset was invented?

+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

        /*
+        * 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)));
+       }

Why? This seems like an entirely arbitrary prohibition. If inserts
aren't supported, then they'll fail at runtime. Same for updates or
deletes, for which you have not added checks. I think you should just
remove this.

+               /* Force inheritance recursion, if partitioned table. */
+               if (recurse || relid_is_partitioned(myrelid))

Disagree with this, too. There's no reason for partitioned tables to
be special in this way. Similarly, disagree with all of the places
that do something similar.

-                               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")));

You have a few of these; they cause problems for translators, because
different languages have different word ordering. Repeat the entire
message instead: is_attach_partition ? "column \"%s\" in source table
must be marked NOT NULL" : "column \"%s\" in child table must be
marked NOT NULL".

+-- XXX add ownership tests

So do that. :-)

+ERROR:  column "b" is not null in parent
+HINT:  Please drop not null in the parent instead

Hmm. That hint doesn't seem like project style, and I'm not sure
that it really makes sense to issue such a hint anyway. Who knows
whether that is the right thing to do? I think you should somehow be
complaining about the fact that this is a partition, rather than
complaining about the fact that the column is NOT NULL in the parent.
Are we insisting that the flags match exactly, or only that the child
may not allow nulls unless the parent does?

+ERROR: new partition's list of values overlaps with partition
"lpart1" of "list_parted"

Maybe just:

ERROR: partitions must not overlap
-or-
ERROR: partition "%s" would overlap partition "%s"

As before, this is just an initial read-through, so apologies for
whatever I may have missed.

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

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

#8Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#7)
Re: Declarative partitioning - another take
+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

It looks like in most of the places where this function is called it's
using relid_is_partition(RelationGetRelid(rel)). Instead probably we should
check existence of rd_partdesc or rd_partkey within Relation() and make
sure that those members are always set for a partitioned table. That will
avoid cache lookup and may give better performance.

That brings up another question. Can we have rd_partdesc non null and
rd_partkey null or vice-versa. If not, should we club those into a single
structure like Partition (similar to Relation)?

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#9Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#8)
Re: Declarative partitioning - another take

On 2016/08/17 14:33, Ashutosh Bapat wrote:

+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

It looks like in most of the places where this function is called it's
using relid_is_partition(RelationGetRelid(rel)). Instead probably we should
check existence of rd_partdesc or rd_partkey within Relation() and make
sure that those members are always set for a partitioned table. That will
avoid cache lookup and may give better performance.

It seems you are talking about a *partitioned* relation here, whereas
relid_is_partition() is to trying to check if a relation is *partition* by
looking up the pg_partition catalog (or the associated cache). For the
former, the test you suggest or rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE test is enough.

I am slightly tempted to eliminate the pg_partition catalog and associated
syscache altogether and add a column to pg_class as Robert suggested.
That way, all relid_is_partition() calls will be replaced by
rel->rd_partbound != NULL check. But one potential problem with that
approach is that now whenever a parent relation is opened, all the
partition relations must be opened to get the partbound value (to form the
PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
currently, we just look up the pg_partition catalog (or the associated
cache) for every partition and that gets us the partbound.

That brings up another question. Can we have rd_partdesc non null and
rd_partkey null or vice-versa. If not, should we club those into a single
structure like Partition (similar to Relation)?

It's true that rd_partkey and rd_partdesc are both either NULL or
non-NULL, so combining them into a single struct is an idea worth considering.

Thanks,
Amit

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

#10Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#9)
Re: Declarative partitioning - another take

On Wed, Aug 17, 2016 at 11:51 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/08/17 14:33, Ashutosh Bapat wrote:

+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID,

ObjectIdGetDatum(relid));

+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

It looks like in most of the places where this function is called it's
using relid_is_partition(RelationGetRelid(rel)). Instead probably we

should

check existence of rd_partdesc or rd_partkey within Relation() and make
sure that those members are always set for a partitioned table. That will
avoid cache lookup and may give better performance.

It seems you are talking about a *partitioned* relation here, whereas
relid_is_partition() is to trying to check if a relation is *partition* by
looking up the pg_partition catalog (or the associated cache). For the
former, the test you suggest or rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE test is enough.

Uh, you are right. Sorry for my misunderstanding.

I am slightly tempted to eliminate the pg_partition catalog and associated
syscache altogether and add a column to pg_class as Robert suggested.
That way, all relid_is_partition() calls will be replaced by
rel->rd_partbound != NULL check. But one potential problem with that
approach is that now whenever a parent relation is opened, all the
partition relations must be opened to get the partbound value (to form the
PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
currently, we just look up the pg_partition catalog (or the associated
cache) for every partition and that gets us the partbound.

That brings up another question. Can we have rd_partdesc non null and
rd_partkey null or vice-versa. If not, should we club those into a single
structure like Partition (similar to Relation)?

It's true that rd_partkey and rd_partdesc are both either NULL or
non-NULL, so combining them into a single struct is an idea worth
considering.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#11Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#9)
Re: Declarative partitioning - another take

On Wed, Aug 17, 2016 at 2:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I am slightly tempted to eliminate the pg_partition catalog and associated
syscache altogether and add a column to pg_class as Robert suggested.
That way, all relid_is_partition() calls will be replaced by
rel->rd_partbound != NULL check. But one potential problem with that
approach is that now whenever a parent relation is opened, all the
partition relations must be opened to get the partbound value (to form the
PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
currently, we just look up the pg_partition catalog (or the associated
cache) for every partition and that gets us the partbound.

Well, you could just look up the pg_class row without opening the
relation, too. There is a system cache on pg_class.oid, after all. I
think the issue is whether it's safe to read either one of those
things without a lock on the child relation. If altering the
partitioning information for a relation requires holding only
AccessExclusiveLock on that relation, and no lock on the parent, then
you really can't read the information for any child relation without
taking at least AccessShareLock. Otherwise, it might change under
you, and that would be bad.

I'm inclined to think that changing the partitioning information for a
child is going to require AccessExclusiveLock on both the child and
the parent. That seems unfortunate from a concurrency point of view,
but we may be stuck with it: suppose you require only
ShareUpdateExclusiveLock on the parent. Well, then a concurrent read
transaction might see the partition boundaries change when it does a
relcache rebuild, which would cause it to suddenly start expecting the
data to be in a different plan in mid-transaction, perhaps even in
mid-scan. Maybe that's survivable with really careful coding, but it
seems like it's probably a bad thing. For example, it would mean that
the executor would be unable to rely on the partitioning information
in the relcache remaining stable underneath it. Moreover, the
relcache is always going to be scanned with the most recent possible
MVCC snapshot, but the transaction snapshot may be older, so such a
system creates all sorts of nasty possibilities for there to be skew
between the snapshot being used to via the data and the snapshot being
used to read the metadata that says where the data is.

This may need some more thought, but if we go with that approach of
requiring an AccessExclusiveLock on both parent and child, then it
seems to me that maybe we should consider the partitioning information
to be a property of the parent rather than the child. Just take all
the partitioning information for all children and put it in one big
node tree and store it in the pg_class or pg_partition_root entry for
the parent as one big ol' varlena. Now you can open the parent and
get all of the partitioning information for all of the children
without needing any lock on any child, and that's *really* good,
because it means that some day we might be able to do partition
elimination before locking any of the children! That would be
excellent.

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

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

#12Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#1)
Re: Declarative partitioning - another take

The parent-child relationship of multi-level partitioned tables is not
retained when creating the AppendRelInfo nodes. We create RelOptInfo nodes
for all the leaf and intermediate tables. The AppendRelInfo nodes created
for these RelOptInfos set the topmost table as the parent of all the leaf
and child tables. Since partitioning scheme/s at each level is/are
associated with the parent/s at that level, we loose information about the
immediate parents and thus it becomes difficult to identify which leaf node
falls where in the partition hierarchy. This stops us from doing any
lump-sum partition pruning where we can eliminate all the partitions under
a given parent-partition if that parent-partition gets pruned. It also
restricts partition-wise join technique from being applied to partial
partition hierarchy when the whole partitioning scheme of joining tables
does not match. Maintaining a RelOptInfo hierarchy should not create
corresponding Append (all kinds) plan hierarchy since
accumulate_append_subpath() flattens any such hierarchy while creating
paths. Can you please consider this point in your upcoming patch?

On Wed, Aug 10, 2016 at 4:39 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Hi,

Attached is the latest set of patches to implement declarative
partitioning. There is already a commitfest entry for the same:
https://commitfest.postgresql.org/10/611/

The old discussion is here:
/messages/by-id/55D3093C.5010800@lab.ntt.co.jp/

Attached patches are described below:

0001-Catalog-and-DDL-for-partition-key.patch
0002-psql-and-pg_dump-support-for-partitioned-tables.patch

These patches create the infrastructure and DDL for partitioned
tables.

In addition to a catalog for storing the partition key information, this
adds a new relkind to pg_class.h. PARTITION BY clause is added to CREATE
TABLE. Tables so created are RELKIND_PARTITIONED_REL relations which are
to be special in a number of ways, especially with regard to their
interactions with regular table inheritance features.

PARTITION BY RANGE ({ column_name | ( expression ) } [ opclass ] [, ...])
PARTITION BY LIST ({ column_name | ( expression ) } [ opclass ])

0003-Catalog-and-DDL-for-partition-bounds.patch
0004-psql-and-pg_dump-support-for-partitions.patch

These patches create the infrastructure and DDL for partitions.

Parent-child relationships of a partitioned table and its partitions are
managed behind-the-scenes with inheritance. That means there is a
pg_inherits entry and attributes, constraints, etc. are marked with
inheritance related information appropriately. However this case differs
from a regular inheritance relationship in a number of ways. While the
regular inheritance imposes certain restrictions on what elements a
child's schema is allowed to contain (both at creation time and
after-the-fact), the partitioning related code imposes further
restrictions. For example, while regular inheritance allows a child to
contain its own columns, the partitioning code disallows that. Stuff like
NO INHERIT marking on check constraints, ONLY are ignored by the the
partitioning code.

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

CREATE TABLE partition_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
[ PARTITION BY {RANGE | LIST} ( { column_name | ( expression ) } [ opclass
] [, ...] )

CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
SERVER server_name
[ OPTIONS ( option 'value' [, ... ] ) ]

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

ALTER TABLE parent DETACH PARTITION partition_name

partition_bound_spec is:

FOR VALUES { list_spec | range_spec }

list_spec in FOR VALUES is:

IN ( expression [, ...] )

range_spec in FOR VALUES is:

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

where lower-bound and upper-bound are:

{ ( expression [, ...] ) | UNBOUNDED }

expression can be a string literal, a numeric literal or NULL.

Note that the one can specify PARTITION BY when creating a partition
itself. That is to allow creating multi-level partitioned tables.

0005-Teach-a-few-places-to-use-partition-check-constraint.patch

A partition's bound implicitly constrains the values that are allowed in
the partition key of its rows. The same can be applied to partitions when
inserting data *directly* into them to make sure that only the correct
data is allowed in (if a tuple has been routed from the parent, that
becomes unnecessary). To that end, ExecConstraints() now includes the
above implicit check constraint in the list of constraints it enforces.

Further, to enable constraint based partition exclusion on partitioned
tables, the planner code includes in its list of constraints the above
implicitly defined constraints. This arrangement is temporary however and
will be rendered unnecessary when we implement special data structures and
algorithms within the planner in future versions of this patch to use
partition metadata more effectively for partition exclusion.

Note that the "constraints" referred to above are not some on-disk
structures but those generated internally on-the-fly when requested by a
caller.

0006-Introduce-a-PartitionTreeNode-data-structure.patch
0007-Tuple-routing-for-partitioned-tables.patch

These patches enable routing of tuples inserted into a partitioned table
to one of its leaf partitions. It applies to both COPY FROM and INSERT.
First of these patches introduces a data structure that provides a
convenient means for the tuple routing code to step down a partition tree
one level at a time. The second one modifies copy.c and executor to
implement actual tuple routing. When inserting into a partition, its row
constraints and triggers are applied. Note that the partition's
constraints also include the constraints defined on the parent. This
arrangements means however that the parent's triggers are not currently
applied.

Updates are handled like they are now for inheritance sets, however, if an
update makes a row change partition, an error will be thrown.

0008-Update-DDL-Partitioning-chapter.patch

This patch updates the partitioning section in the DDL chapter to reflect
the new methods made available for creating and managing partitioned table
and its partitions. Especially considering that it is no longer necessary
to define CHECK constraints and triggers/rules manually for constraint
exclusion and tuple routing, respectively.

TODO (in short term):
* Add more regression tests and docs
* Add PartitionOptInfo and use it to perform partition pruning more
effectively (the added infrastructure should also help pairwise joins
patch proposed by Ashutosh Bapat [1])
* Fix internal representation of list partition bounds to be more efficient

Thanks,
Amit

[1]
/messages/by-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXn
a-urjm_UY9BqXj%3DEaDTSA%40mail.gmail.com

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

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#13Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#12)
Re: Declarative partitioning - another take

On 2016/08/22 13:51, Ashutosh Bapat wrote:

The parent-child relationship of multi-level partitioned tables is not
retained when creating the AppendRelInfo nodes. We create RelOptInfo nodes
for all the leaf and intermediate tables. The AppendRelInfo nodes created
for these RelOptInfos set the topmost table as the parent of all the leaf
and child tables. Since partitioning scheme/s at each level is/are
associated with the parent/s at that level, we loose information about the
immediate parents and thus it becomes difficult to identify which leaf node
falls where in the partition hierarchy. This stops us from doing any
lump-sum partition pruning where we can eliminate all the partitions under
a given parent-partition if that parent-partition gets pruned. It also
restricts partition-wise join technique from being applied to partial
partition hierarchy when the whole partitioning scheme of joining tables
does not match. Maintaining a RelOptInfo hierarchy should not create
corresponding Append (all kinds) plan hierarchy since
accumulate_append_subpath() flattens any such hierarchy while creating
paths. Can you please consider this point in your upcoming patch?

I agree. So there seem to be two things here: a) when expanding a
partitioned table inheritance set, do it recursively such that resulting
AppendRelInfos preserve *immediate* parent-child relationship info. b)
when accumulating append subpaths, do not flatten a subpath that is itself
an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
non-NULL partitioning info. Is the latter somehow necessary for
pairwise-join considerations?

I think I can manage to squeeze in (a) in the next version patch and will
also start working on (b), mainly the part about RelOptInfo getting some
partitioning info.

Thanks,
Amit

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

#14Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#13)
Re: Declarative partitioning - another take

On Thu, Aug 25, 2016 at 12:22 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/08/22 13:51, Ashutosh Bapat wrote:

The parent-child relationship of multi-level partitioned tables is not
retained when creating the AppendRelInfo nodes. We create RelOptInfo

nodes

for all the leaf and intermediate tables. The AppendRelInfo nodes created
for these RelOptInfos set the topmost table as the parent of all the leaf
and child tables. Since partitioning scheme/s at each level is/are
associated with the parent/s at that level, we loose information about

the

immediate parents and thus it becomes difficult to identify which leaf

node

falls where in the partition hierarchy. This stops us from doing any
lump-sum partition pruning where we can eliminate all the partitions

under

a given parent-partition if that parent-partition gets pruned. It also
restricts partition-wise join technique from being applied to partial
partition hierarchy when the whole partitioning scheme of joining tables
does not match. Maintaining a RelOptInfo hierarchy should not create
corresponding Append (all kinds) plan hierarchy since
accumulate_append_subpath() flattens any such hierarchy while creating
paths. Can you please consider this point in your upcoming patch?

I agree. So there seem to be two things here: a) when expanding a
partitioned table inheritance set, do it recursively such that resulting
AppendRelInfos preserve *immediate* parent-child relationship info.

Right.

b)
when accumulating append subpaths, do not flatten a subpath that is itself
an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
non-NULL partitioning info.Is the latter somehow necessary for
pairwise-join considerations?

I don't think you need to do anything in the path creation code for this.
As is it flattens all AppendPath hierarchies whether for partitioning or
inheritance or subqueries. We should leave it as it is.

I think I can manage to squeeze in (a) in the next version patch and will
also start working on (b), mainly the part about RelOptInfo getting some
partitioning info.

I am fine with b, where you would include some partitioning information in
RelOptInfo. But you don't need to do what you said in (b) above.

In a private conversation Robert Haas suggested a way slightly different
than what my patch for partition-wise join does. He suggested that the
partitioning schemes i.e strategy, number of partitions and bounds of the
partitioned elations involved in the query should be stored in PlannerInfo
in the form of a list. Each partitioning scheme is annotated with the
relids of the partitioned relations. RelOptInfo of the partitioned relation
will point to the partitioning scheme in PlannerInfo. Along-with that each
RelOptInfo will need to store partition keys for corresponding relation.
This simplifies matching the partitioning schemes of the joining relations.
Also it reduces the number of copies of partition bounds floating around as
we expect that a query will involve multiple partitioned tables following
similar partitioning schemes. May be you want to consider this idea while
working on (b).

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#15Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#11)
Re: Declarative partitioning - another take

On 2016/08/18 5:23, Robert Haas wrote:

On Wed, Aug 17, 2016 at 2:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I am slightly tempted to eliminate the pg_partition catalog and associated
syscache altogether and add a column to pg_class as Robert suggested.
That way, all relid_is_partition() calls will be replaced by
rel->rd_partbound != NULL check. But one potential problem with that
approach is that now whenever a parent relation is opened, all the
partition relations must be opened to get the partbound value (to form the
PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
currently, we just look up the pg_partition catalog (or the associated
cache) for every partition and that gets us the partbound.

Well, you could just look up the pg_class row without opening the
relation, too. There is a system cache on pg_class.oid, after all. I

Yes, I somehow didn't think of that.

think the issue is whether it's safe to read either one of those
things without a lock on the child relation. If altering the
partitioning information for a relation requires holding only
AccessExclusiveLock on that relation, and no lock on the parent, then
you really can't read the information for any child relation without
taking at least AccessShareLock. Otherwise, it might change under
you, and that would be bad.

I'd imagine this won't be a problem because we take an AccessExclusiveLock
on the parent when adding/removing a partition.

I'm inclined to think that changing the partitioning information for a
child is going to require AccessExclusiveLock on both the child and
the parent. That seems unfortunate from a concurrency point of view,
but we may be stuck with it: suppose you require only
ShareUpdateExclusiveLock on the parent. Well, then a concurrent read
transaction might see the partition boundaries change when it does a
relcache rebuild, which would cause it to suddenly start expecting the
data to be in a different plan in mid-transaction, perhaps even in
mid-scan. Maybe that's survivable with really careful coding, but it
seems like it's probably a bad thing. For example, it would mean that
the executor would be unable to rely on the partitioning information
in the relcache remaining stable underneath it. Moreover, the
relcache is always going to be scanned with the most recent possible
MVCC snapshot, but the transaction snapshot may be older, so such a
system creates all sorts of nasty possibilities for there to be skew
between the snapshot being used to via the data and the snapshot being
used to read the metadata that says where the data is.

We do take a lock on the parent because we would be changing its partition
descriptor (relcache). I changed MergeAttributes() such that an
AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
parent is a partitioned table.

This may need some more thought, but if we go with that approach of
requiring an AccessExclusiveLock on both parent and child, then it
seems to me that maybe we should consider the partitioning information
to be a property of the parent rather than the child. Just take all
the partitioning information for all children and put it in one big
node tree and store it in the pg_class or pg_partition_root entry for
the parent as one big ol' varlena. Now you can open the parent and
get all of the partitioning information for all of the children
without needing any lock on any child, and that's *really* good,
because it means that some day we might be able to do partition
elimination before locking any of the children! That would be
excellent.

If we need an AccessExclusiveLock on parent to add/remove a partition
(IOW, changing that child table's partitioning information), then do we
need to lock the individual partitions when reading partition's
information? I mean to ask why the simple syscache look-ups to get each
partition's bound wouldn't do.

Thanks,
Amit

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

#16Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#7)
9 attachment(s)
Re: Declarative partitioning - another take

Sorry it took me a while to reply. Attached updated patches including the
review comments on 0001 at [1]/messages/by-id/CA+TgmoZ008qTgd_Qg6_oZb3i0mOYrS6MdhncwgcqPKahixjarg@mail.gmail.com.

On 2016/08/17 3:54, Robert Haas wrote:

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

0002-psql-and-pg_dump-support-for-partitioned-tables.patch

+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')

Version check is redundant, right?

Yep, fixed.

+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers
+--------+---------+-----------
+ a      | integer |
+ b      | integer |
+Partition Key: PARTITION BY RANGE (((a + b)))

I understand that it's probably difficult not to end up with two sets
of parentheses here, but can we avoid ending up with three sets?

Fixed. This code was copy-pasted from the index code which has other
considerations for adding surrounding parentheses which don't apply to the
partition key code.

Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
pg_dump can add that part back. Then this could say:

Partition Key: RANGE ((a + b))

...which seems a good deal more natural than what you have now.

Agreed, so done that way.

0003-Catalog-and-DDL-for-partition-bounds.patch

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

Hmm, I don't think I like this. Why should it be necessary to detach
a partition before dropping it? That seems like an unnecessary step.

I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.

[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
IMMEDIATE ]
+
</synopsis>

Unnecessary hunk.

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

Sentence fragment.

Fixed.

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

This is another restriction I don't understand. Why can't I attach a
partitioned table?

I removed this restriction.

ATExecAttachPartition() adds a AT work queue entry for the table being
attached to perform a heap scan in the rewrite phase. The scan is done
for checking that no row violates the partition boundary condition. When
attaching a partitioned table as partition, multiple AT work queue entries
are now added - one for each leaf partition of the table being attached.

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

Ugh, why? I think this should work exactly the same way for
partitioned tables that it does for any other inheritance hierarchy.
Sure, you'll get no rows, but who cares?

Agreed, done that way.

+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES START ('2016-07-01') END
('2016-08-01');
+    SERVER server_07;

Extra semicolon?

Fixed.

+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be

I think experience suggests that this is a good restriction, but then
why does the syntax synopsis indicate that PARTITION BY can be
specified along with column definitions? Similarly for CREATE FOREIGN
TABLE.

The syntax synopsis of CREATE TABLE ... PARTITION OF indicates that a list
of column WITH OPTION and/or table_constraint can be specified. It does
not allow column definitions.

In this case, inherited columns will be listed in the PARTITION BY clause.

Do you mean that the CREATE TABLE ... PARTITION OF syntax should allow
column definitions just like INHERITS does and unlike regular inheritance,
throw error if columns other than those to be merged are found?

+      When specifying for a table being created as partition, one needs to
+      use column names from the parent table as part of the key.

This is not very clear.

Sentence removed because it may be clear from the context that inherited
columns are to be used for the partition key.

-       /* 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;

It might be right to disallow NO INHERIT in this case, but I don't
think it can be right to just silently ignore it.

OK, I changed this to instead throw an error if a NO INHERIT check
constraint is added to a partitioned table or a partition.

+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.

This seems unlikely to be safe, unless I'm missing something.

Like TupleDesc, a table's PartitionDesc is preserved across relcache
rebuilds. PartitionDesc consists of arrays of OIDs and partition bounds
for a table's immediate partitions. It can only change by adding/removing
partitions to/from the table which requires an exclusive lock on it.
Since this data can grow arbitrarily big it seemed better to not have to
copy it around, so a direct pointer to the relcache field (rd_partdesc) is
given to callers. relcache rebuilds that do not logically change
PartitionDesc leave it intact so that some user of it is not left with a
dangling pointer. Am I missing something?

+       form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+       systable_endscan(scan);
+       heap_close(catalogRelation, AccessShareLock);
+
+       return form->inhparent;

This is unsafe. After systable_endscan, it is no longer OK to access
form->inhparent.

Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.

There should probably be a note in the function header comment that it
is unsafe to use this for an inheritance child that is not a
partition, because there could be more than one parent in that case.
Or maybe the whole idea of this function just isn't very sound...

Fixed unsafe coding and added a comment to the function saying it should
be called on tables known to be partitions.

+static List *
+get_partitions(Oid relid, int lockmode)
+{
+       return find_inheritance_children(relid, lockmode);
+}

What's the point? If we're going to have a wrapper here at all, then
shouldn't it have a name that matches the existing convention - e.g.
find_partitions() or find_child_partitions()? But I think you might
as well just use find_inheritance_children() directly.

OK, changed to just use find_inheritance_children() directly.

+                * Happens when we have created the pg_inherits entry
but not the
+                * pg_partition entry yet.

Why do we ever allow the flow of control to reach this point while we
are in such an intermediate state?

Fixed to prevent this from happening.

+free_partition_info(PartitionInfoData **p, int num)

Seems very error-prone. Isn't this why MemoryContextReset was invented?

Got rid of this function.

+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

OK, I got rid of the pg_partition catalog and added a pg_class attribute
for storing partition bound. Because the newly added attribute is a
pg_node_tree and hence not readily accessible without a heap_getattr()
call, I added a boolean relispartition as well. Now all the
relid_is_partition() calls have been replaced by checks using
relation->rd_rel->relispartition.

/*
+        * 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)));
+       }

Why? This seems like an entirely arbitrary prohibition. If inserts
aren't supported, then they'll fail at runtime. Same for updates or
deletes, for which you have not added checks. I think you should just
remove this.

OK, removed this check.

Instead, ExecInitModifyTable()/BeginCopy() call CheckValidResultRel() for
every leaf partition to check that they are ready for CMD_INSERT.

+               /* Force inheritance recursion, if partitioned table. */
+               if (recurse || relid_is_partitioned(myrelid))

Disagree with this, too. There's no reason for partitioned tables to
be special in this way. Similarly, disagree with all of the places
that do something similar.

Removed the forced recursion bit here and a few other places.

-                               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")));

You have a few of these; they cause problems for translators, because
different languages have different word ordering. Repeat the entire
message instead: is_attach_partition ? "column \"%s\" in source table
must be marked NOT NULL" : "column \"%s\" in child table must be
marked NOT NULL".

Changed so that the entire message is repeated.

+-- XXX add ownership tests

So do that. :-)

Done, sorry about that.

+ERROR:  column "b" is not null in parent
+HINT:  Please drop not null in the parent instead

Hmm. That hint doesn't seem like project style, and I'm not sure
that it really makes sense to issue such a hint anyway. Who knows
whether that is the right thing to do? I think you should somehow be
complaining about the fact that this is a partition, rather than
complaining about the fact that the column is NOT NULL in the parent.
Are we insisting that the flags match exactly, or only that the child
may not allow nulls unless the parent does?

I think the latter. It is assumed that all the parent's constraints are
present in a child table, because in ExecInsert()/CopyFrom() we perform
ExecConstraints() using the child relation even if the actual insert was
on the parent. Also, if an inherited NOT NULL constraint on a child's
column is dropped irrespective of parent's, selecting a parent's NOT NULL
column might return nulls from the child table that no longer has the
constraint.

I recently came across a related proposal whereby dropping *inherited* NOT
NULL from child tables will be prevented. Problems in letting it be be
dropped are mentioned here:

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

That proposal is probably being reworked such that NOT NULL constraints
get a pg_constraint entry with proper accounting of inheritance count.

+ERROR: new partition's list of values overlaps with partition
"lpart1" of "list_parted"

Maybe just:

ERROR: partitions must not overlap
-or-
ERROR: partition "%s" would overlap partition "%s"

OK, I changed to the second message.

As before, this is just an initial read-through, so apologies for
whatever I may have missed.

Thanks a lot for the review.

By the way, I am still working on the following items and will be included
in the next version of the patch.

* Fix internal representation of list partition bounds to be more efficient
* Add PartitionOptInfo

As mentioned in [2]/messages/by-id/f2a9592a-17e9-4c6a-e021-03b802195ce7@lab.ntt.co.jp, I have refactored the inheritance expansion code
within optimizer so that a partitioned table's inheritance hierarchy is
preserved in resulting AppendRelInfos (patch 0005). One immediate benefit
of that is that if constraint exclusion determines that an intermediate
partition is to be excluded then all partitions underneath it are excluded
automatically. With the current flattened model, all partitions in that
subtree would have been processed and excluded one-by-one.

Regards,
Amit

[1]: /messages/by-id/CA+TgmoZ008qTgd_Qg6_oZb3i0mOYrS6MdhncwgcqPKahixjarg@mail.gmail.com
/messages/by-id/CA+TgmoZ008qTgd_Qg6_oZb3i0mOYrS6MdhncwgcqPKahixjarg@mail.gmail.com
[2]: /messages/by-id/f2a9592a-17e9-4c6a-e021-03b802195ce7@lab.ntt.co.jp
/messages/by-id/f2a9592a-17e9-4c6a-e021-03b802195ce7@lab.ntt.co.jp

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-2.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-2.patchDownload
From a5ba9cd74b09b1f5ea5833fb938a9b77ec7dab7d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                    |  102 +++++++-
 doc/src/sgml/ref/create_table.sgml            |   55 ++++
 src/backend/access/common/reloptions.c        |    2 +
 src/backend/catalog/Makefile                  |    6 +-
 src/backend/catalog/aclchk.c                  |    2 +
 src/backend/catalog/dependency.c              |    2 +
 src/backend/catalog/heap.c                    |   27 ++-
 src/backend/catalog/objectaddress.c           |    5 +-
 src/backend/catalog/partition.c               |  396 +++++++++++++++++++++++++
 src/backend/catalog/pg_depend.c               |    3 +
 src/backend/catalog/pg_partitioned_table.c    |  172 +++++++++++
 src/backend/commands/analyze.c                |    2 +
 src/backend/commands/copy.c                   |    6 +
 src/backend/commands/indexcmds.c              |    7 +-
 src/backend/commands/lockcmds.c               |    2 +-
 src/backend/commands/policy.c                 |    2 +-
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/sequence.c               |    1 +
 src/backend/commands/tablecmds.c              |  384 +++++++++++++++++++++++-
 src/backend/commands/trigger.c                |    7 +-
 src/backend/commands/vacuum.c                 |    1 +
 src/backend/executor/execMain.c               |    2 +
 src/backend/executor/nodeModifyTable.c        |    1 +
 src/backend/nodes/copyfuncs.c                 |   33 ++
 src/backend/nodes/equalfuncs.c                |   28 ++
 src/backend/nodes/outfuncs.c                  |   26 ++
 src/backend/parser/gram.y                     |  110 ++++++--
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_expr.c               |    5 +
 src/backend/parser/parse_utilcmd.c            |   69 +++++
 src/backend/rewrite/rewriteDefine.c           |    1 +
 src/backend/rewrite/rewriteHandler.c          |    1 +
 src/backend/tcop/utility.c                    |    5 +-
 src/backend/utils/cache/relcache.c            |   19 ++-
 src/backend/utils/cache/syscache.c            |   12 +
 src/include/catalog/dependency.h              |    8 +-
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/partition.h               |   35 +++
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_partitioned_table.h    |   69 +++++
 src/include/catalog/pg_partitioned_table_fn.h |   29 ++
 src/include/commands/defrem.h                 |    2 +
 src/include/nodes/nodes.h                     |    2 +
 src/include/nodes/parsenodes.h                |   35 +++
 src/include/parser/kwlist.h                   |    1 +
 src/include/parser/parse_node.h               |    3 +-
 src/include/pg_config_manual.h                |    5 +
 src/include/utils/rel.h                       |    9 +
 src/include/utils/syscache.h                  |    1 +
 src/test/regress/expected/alter_table.out     |   46 +++
 src/test/regress/expected/create_table.out    |  155 ++++++++++
 src/test/regress/expected/sanity_check.out    |    1 +
 src/test/regress/sql/alter_table.sql          |   34 +++
 src/test/regress/sql/create_table.sql         |  133 +++++++++
 54 files changed, 2035 insertions(+), 45 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/backend/catalog/pg_partitioned_table.c
 create mode 100644 src/include/catalog/partition.h
 create mode 100644 src/include/catalog/pg_partitioned_table.h
 create mode 100644 src/include/catalog/pg_partitioned_table_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4e09e06..84e3faf 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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>partedrelid</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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partkey</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns and/or expressions in the partition key and partition
+      rules associated with the partitions.
+     </para>
+
+     <para>
+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ba1f3aa..09e29d1 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_type.o storage.o toasting.o pg_partitioned_table.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_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..679f1c7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
 					 getObjectDescription(object));
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				subflags = 0;	/* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
+	/* Remove NO INHERIT flag if rel is a partitioned table */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
 	/*
 	 * Create the Check Constraint
 	 */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8068b82..5432b33 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..e8f1e94
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,396 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.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_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+	Oid		*typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+	char	  **partcolnames;	/* partition key column names */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+								KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	int2vector	   *partattrs;
+	oidvector	   *opclass;
+	KeyTypeCollInfo *tcinfo;
+	ListCell	   *partexprbin_item;
+	List		   *partexprsrc;
+	ListCell	   *partexprsrc_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	/* Allocate in the supposedly short-lived working context */
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/* Open the catalog for its tuple descriptor */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	partattrs = (int2vector *) DatumGetPointer(datum);
+
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprbin,
+						 RelationGetDescr(catalog),
+						 &isnull);
+
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+
+		/* We should have a partexprsrc as well */
+		datum = heap_getattr(tuple,
+							 Anum_pg_partitioned_table_partexprsrc,
+							 RelationGetDescr(catalog),
+							 &isnull);
+		Assert(!isnull);
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+		partexprsrc = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+	tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+	tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather partition column names (simple C strings) */
+	key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+	/* Copy partattrs and fill other per-attribute info */
+	partexprbin_item = list_head(key->partexprs);
+	partexprsrc_item = list_head(partexprsrc);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		HeapTuple		tuple;
+		AttrNumber		attno;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		key->partattrs[i] = attno = partattrs->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+			tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+			tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+		}
+		get_typlenbyvalalign(tcinfo->typid[i],
+							 &tcinfo->typlen[i],
+							 &tcinfo->typbyval[i],
+							 &tcinfo->typalign[i]);
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+
+		/* Collect atttribute names */
+		if (key->partattrs[i] != 0)
+			key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+															key->partattrs[i]);
+		else
+		{
+			Value *str = lfirst(partexprsrc_item);
+			key->partcolnames[i] = pstrdup(str->val.str);
+			partexprsrc_item = lnext(partexprsrc_item);
+		}
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									 RelationGetRelationName(relation),
+									 ALLOCSET_SMALL_MINSIZE,
+									 ALLOCSET_SMALL_INITSIZE,
+									 ALLOCSET_SMALL_MAXSIZE);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+	return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				i;
+
+	newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *)
+							palloc0(newkey->partnatts * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs,
+							newkey->partnatts * sizeof(AttrNumber));
+
+	newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *)
+							palloc0(newkey->partnatts * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+	newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+											 fromkey->tcinfo);
+	newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+	for (i = 0; i < newkey->partnatts; i++)
+		newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+	return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+	KeyTypeCollInfo   *result = (KeyTypeCollInfo *)
+								palloc0(sizeof(KeyTypeCollInfo));
+
+	result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+	result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+	memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+	result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+	memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+	result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+	result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+	result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+	return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
 
+	if (behavior == DEPENDENCY_IGNORE)
+		return;					/* nothing to do */
+
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
 
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..45ec347
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ *	  routines to support manipulation of the pg_partitioned_table 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_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprbin,
+				  List *partexprsrc,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprbinDatum;
+	Datum		partexprsrcDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/*
+	 * Copy the partition key, opclass info into arrays (should we
+	 * make the caller pass them like this to start with?)
+	 */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprbin)
+	{
+		char       *exprbinString;
+		char       *exprsrcString;
+
+		exprbinString = nodeToString(partexprbin);
+		exprsrcString = nodeToString(partexprsrc);
+		partexprbinDatum = CStringGetTextDatum(exprbinString);
+		partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+		pfree(exprbinString);
+		pfree(exprsrcString);
+	}
+	else
+		partexprbinDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprbinDatum)
+	{
+		nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+		nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+	}
+
+	values[Anum_pg_partitioned_table_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+	values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprbin)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprbin,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_IGNORE);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9ac7122..bb67004 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1323,6 +1324,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f45b330..5120a4d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..279cf99 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bc2e4af..bd99fb6 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..e716032 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
 	bool		concurrent;
 };
 
+/* for find_attr_reference_walker */
+typedef struct
+{
+	AttrNumber	attnum;
+} find_attr_reference_context;
+
 /* Alter table target-type flags for ATSimplePermissions */
 #define		ATT_TABLE				0x0001
 #define		ATT_VIEW				0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass);
 
 
 /* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partby)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprbin = NIL;
+		List		   *partexprsrc = NIL;
+
+		stmt->partby = transformPartitionBy(rel, stmt->partby);
+		ComputePartitionAttrs(rel, stmt->partby->partParams,
+							  partattrs, &partexprbin, &partexprsrc,
+							  partopclass);
+
+		partnatts = list_length(stmt->partby->partParams);
+		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+						  partattrs, partexprbin, partexprsrc, partopclass);
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
+	 * RELKIND_RELATION while the relation is actually a partitioned table.
+	 */
+	if (classform->relkind != relkind &&
+				(relkind == RELKIND_RELATION &&
+					classform->relkind != RELKIND_PARTITIONED_TABLE))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var		   *variable = (Var *) node;
+		AttrNumber	attnum = variable->varattno;
+
+		if (attnum == context->attnum)
+			return true;
+	}
+
+	return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber partatt = get_partition_col_attnum(key, i);
+
+		if(partatt != 0)
+		{
+			*is_expr = false;
+			if (attnum == partatt)
+				return true;
+		}
+		else
+		{
+			find_attr_reference_context context;
+
+			*is_expr = true;
+			context.attnum = attnum;
+			if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+				return true;
+
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+	PartitionBy	   *partby;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	partby = (PartitionBy *) makeNode(PartitionBy);
+
+	partby->strategy = partitionby->strategy;
+	partby->location = partitionby->location;
+	partby->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+
+			/*
+			 * transformExpr() should have already rejected subqueries,
+			 * aggregates, and window functions, based on the EXPR_KIND_ for
+			 * a partition key expression.
+			 *
+			 * Also reject expressions returning sets; this is for consistency
+			 * DefineRelation() will make more checks.
+			 */
+			if (expression_returns_set(pelem->expr))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key expression cannot return a set"),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		partby->partParams = lappend(partby->partParams, pelem);
+	}
+
+	return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			if (IsA(expr, CollateExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot use COLLATE in partition key expression")));
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				char   *exprsrc;
+
+				partattrs[attn] = 0; /* marks expression */
+				*partexprbin = lappend(*partexprbin, expr);
+
+				/*
+				 * transformExpr() should have already rejected subqueries,
+				 * aggregates, and window functions, based on the EXPR_KIND_
+				 * for a partition key expression.
+				 */
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use a constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				exprsrc = deparse_expression(expr,
+							deparse_context_for(RelationGetRelationName(rel),
+												RelationGetRelid(rel)),
+									   false, false);
+				*partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+			}
+		}
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify an existing btree operator class or define one for the type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 99a659a..501bb93 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0563e63..6905498 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1315,6 +1315,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1877fb4..5668078 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3016,6 +3016,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4171,6 +4172,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+	PartitionBy *newnode = makeNode(PartitionBy);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5085,6 +5112,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionBy:
+			retval = _copyPartitionBy(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..23b7d31 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2630,6 +2631,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3383,6 +3405,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionBy:
+			retval = _equalPartitionBy(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 29b7712..8ad9781 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3279,6 +3280,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3863,6 +3884,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionBy:
+				_outPartitionBy(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cb5cfc4..3d5cde9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partby>		PartitionBy OptPartitionBy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partby = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partby = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partby = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partby = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13768,6 +13841,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * 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 e98fad0..819d403 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partby != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partby)
+	{
+		int		partnatts = list_length(stmt->partby->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -582,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -591,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -673,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -683,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -693,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -707,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -759,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2517,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..6d1d12b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partby != NULL
+													? RELKIND_PARTITIONED_TABLE
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..72fab9e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -1053,6 +1055,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2050,6 +2061,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2991,7 +3004,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5045,6 +5060,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..fb540e1 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partedrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
  * created only during initdb.  The fields for the dependent object
  * contain zeroes.
  *
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question.  However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
  * Other dependency flavors may be needed in future.
  */
 
@@ -77,7 +82,8 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
+	DEPENDENCY_IGNORE = 'g'
 } DependencyType;
 
 /*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..ef50f85 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partedrelid_index, 3351, on pg_partitioned_table using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* 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
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+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);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9a0f6f4
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partedrelid;	/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprbin;	/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+	pg_node_tree	partexprsrc
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partedrelid	1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partexprbin	6
+#define Anum_pg_partitioned_table_partexprsrc	7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprbin,
+					List *partexprsrc,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif   /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..9fe31ce 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionBy,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..2b77579 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionBy
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionBy;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1751,6 +1785,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR:  cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-2.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-2.patchDownload
From 66f989f72bbdeec5ae799ff4a2c3abe76aaba2f5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relationId = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relId), relId);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relId, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relId, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1345e4e..4686cd1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-2.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-2.patchDownload
From d970985a62d97c2ab81c920410abe6552a27feae Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 +++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/heap.c                 |   36 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1421 +++++++++++++++++++++++++++-
 src/backend/catalog/pg_partitioned_table.c |    2 -
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/lockcmds.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  765 +++++++++++++---
 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         |  331 +++++++-
 src/backend/utils/cache/relcache.c         |   82 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   25 +
 src/include/catalog/pg_class.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/test/regress/expected/alter_table.out  |  211 ++++
 src/test/regress/expected/create_table.out |  188 ++++
 src/test/regress/sql/alter_table.sql       |  185 ++++
 src/test/regress/sql/create_table.sql      |  138 +++
 35 files changed, 3964 insertions(+), 164 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 84e3faf..e7912b3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1229,6 +1311,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..007782c 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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..1613218 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"
@@ -90,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -770,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -808,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -818,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -851,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -924,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1033,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1268,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2055,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
-	/* Remove NO INHERIT flag if rel is a partitioned table */
+	/* Remove NO INHERIT flag if rel is a partitioned table or a partition */
 	if (is_no_inherit &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2475,7 +2495,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e8f1e94..8093470 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * partition.c
- *        Partitioning related utility functions.
+ *        Partitioning related data structures and functions.
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
 #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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/ruleutils.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /* Type and collation information for partition key columns */
@@ -69,11 +77,107 @@ 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);
 
+/* Support RelationBuildPartitionDesc() */
+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
  */
@@ -394,3 +498,1318 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
 
 	return result;
 }
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List		   *partoids;
+	ListCell	   *cell;
+	int				i,
+					nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	PartitionInfo  *parts;
+	MemoryContext	oldcxt;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Collect partition info from the catalogs */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+	parts = (PartitionInfoData **)
+							palloc0(nparts * sizeof(PartitionInfoData *));
+
+	i = 0;
+	foreach(cell, partoids)
+	{
+		Oid 		partrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *bound;
+
+		tuple = SearchSysCache1(RELOID, partrelid);
+		Assert(HeapTupleIsValid(tuple));
+
+		parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+		parts[i]->oid = partrelid;
+		datum = SysCacheGetAttr(RELOID, tuple,
+							Anum_pg_class_relpartbound,
+							&isnull);
+		Assert(!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++;
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+
+	oldcxt = MemoryContextSwitchTo(rel->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;
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->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(char *relname, 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("partition \"%s\" would overlap partition \"%s\"",
+							relname, get_rel_name(with)),
+					 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("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (range_overlaps_existing_partition(key, range, pdesc, &with))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(with)),
+						 parser_errposition(pstate, range->location)));
+			break;
+		}
+	}
+
+	heap_close(parent, AccessShareLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * 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 (!rel->rd_rel->relispartition)
+		return NIL;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* Nope, so generate. */
+	return generate_partition_check_qual(rel);
+}
+
+/* Module-local functions */
+
+/*
+ * 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
+		{
+			/*
+			 * Determine operator inclusivity to use from the partition bound
+			 * spec (lowerinc or upperinc).
+			 */
+
+			/* Build leftop ge/gt lower_val */
+			strategy = 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 < 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 partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_check_qual(Relation rel)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_check = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/*
+	 * Generate executable expression using the information in
+	 * ListPartitionSpec and RangePartitionSpec nodes.
+	 */
+
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)), NoLock);
+	my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+	/*
+	 * If parent is also a partition, add its expressions to the list.
+	 */
+	if (parent->rd_rel->relispartition)
+	{
+		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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index 45ec347..5537ac2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
 										RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
 										DEPENDENCY_IGNORE);
-	/* Tell world about the key */
-	CacheInvalidateRelcache(rel);
 
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 	heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 43bbd90..9d80ed5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -677,6 +677,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e716032..0805746 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 3bb5933..e45a83d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,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 */
+	List	   *partition_quals; /* partition check quals 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 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprbin, List **partexprsrc,
 					  Oid *partopclass);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
 	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1452,7 +1494,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1497,8 +1540,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 			/*
 			 * Typed table column option that does not belong to a column from
-			 * the type.  This works because the columns from the type come
-			 * first in the list.
+			 * the type or the partition parent.  This works because the columns
+			 * from the type or the partition parent come first in the list.
 			 */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
 	{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2581,11 +2669,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2827,8 +2916,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3743,6 +3846,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);
@@ -3928,7 +4037,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4008,6 +4117,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4072,6 +4182,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4380,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4583,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4780,6 +4906,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5302,6 +5433,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5983,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5924,9 +6080,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6473,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7877,7 +8037,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);
@@ -7936,8 +8098,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8173,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10176,6 +10345,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10362,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;
 
@@ -10238,37 +10407,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10446,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);
 
@@ -10317,16 +10523,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;
 }
 
 /*
@@ -10377,7 +10575,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
@@ -10395,12 +10593,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10624,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10440,8 +10646,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10456,7 +10664,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10470,7 +10680,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.
@@ -10489,10 +10699,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10539,7 +10753,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10547,7 +10763,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10558,6 +10776,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10571,7 +10790,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10582,6 +10803,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10595,13 +10856,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];
@@ -10610,19 +10869,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10882,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);
 
@@ -10653,11 +10903,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
@@ -10666,7 +10925,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)))
@@ -10728,7 +10987,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);
 
@@ -10759,7 +11018,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)
@@ -10771,30 +11030,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;
 }
 
 /*
@@ -12431,3 +12680,271 @@ ComputePartitionAttrs(Relation rel, 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,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals,
+					   *parent_quals = RelationGetPartitionCheckQual(rel);
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_check_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5668078..8dea2bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3016,6 +3017,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);
@@ -4198,6 +4200,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
  * ****************************************************************
@@ -5118,6 +5157,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 23b7d31..40ad793 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,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);
@@ -2374,6 +2375,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);
@@ -2652,6 +2654,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
  */
@@ -3411,6 +3444,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 8ad9781..3b8fa6b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,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);
@@ -2586,6 +2587,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);
@@ -3301,6 +3303,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'
@@ -3890,6 +3911,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 6f9a81e..466c0f2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2264,6 +2264,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
@@ -2495,6 +2524,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 3d5cde9..b6cce2c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,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
@@ -601,7 +611,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11135,6 +11334,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;
@@ -13734,6 +13934,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13780,6 +13981,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13822,6 +14024,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 819d403..563a41c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,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);
 
 
 /*
@@ -231,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +258,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")));
@@ -359,6 +371,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);
@@ -898,6 +911,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;
@@ -1117,6 +1131,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;
@@ -2586,6 +2601,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2674,6 +2690,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;
@@ -3038,3 +3070,300 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	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_partition_col_name(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_partition_col_name(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_partition_col_name(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 72fab9e..b7404da 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,13 +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_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2063,6 +2101,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2211,11 +2253,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2226,6 +2269,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);
@@ -2256,6 +2300,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
@@ -2311,6 +2358,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 */
@@ -3537,6 +3591,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5062,6 +5130,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..b2782f8 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);
 
@@ -32,4 +45,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
 extern int32 get_partition_col_typmod(PartitionKey key, int col);
 extern char *get_partition_col_name(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(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+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_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..96bea6b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,214 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_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);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
 Partition Key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..bc955e0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,188 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- 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

0004-psql-and-pg_dump-support-for-partitions-2.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-2.patchDownload
From cebe7dd051eae89e007247aeae9650edde6fa99b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   46 ++++++++++++-
 src/test/regress/expected/create_table.out |   23 +++++++
 src/test/regress/sql/create_table.sql      |    6 ++
 7 files changed, 340 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 77ce807..5d90413 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c418ba7..626fa00 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6125,6 +6125,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15302,6 +15359,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15330,8 +15398,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15360,7 +15431,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15425,15 +15497,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15603,6 +15682,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 10d924a..9554f5e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,15 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
+		/* print child tables (exclude, if parent is a partitioned table) */
 		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2b12276..5162c82 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,29 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index c38312e..b29ac79 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,12 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.patchDownload
From 5d36974f4855fd7b6ddf0fe1e08f73aad7ac95e8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
 src/backend/optimizer/prep/prepunion.c |  278 ++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 2 files changed, 204 insertions(+), 83 deletions(-)

diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..592214b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/* Do not flatten the inheritance hierarchy if partitioned table */
+	if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+
+	/* Otherwise, OK to add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (childOID != parentOID)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
-		 */
-		if (oldrc)
-		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
-		}
-
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-2.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-2.patchDownload
From aab5a93200a8d24a10b9c351ac3199cd33e43da3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 ++++++++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 ++++++++
 src/include/nodes/execnodes.h          |    4 ++
 src/test/regress/expected/insert.out   |   76 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/update.out   |   27 +++++++++++
 src/test/regress/sql/insert.sql        |   56 +++++++++++++++++++++++
 src/test/regress/sql/update.sql        |   21 +++++++++
 9 files changed, 280 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 5120a4d..3f5c4e6 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..3d82658 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..70e7d4f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionCheckQual(relation);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..31ca9ed 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-2.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-2.patchDownload
From 2226aaa01957e9805faa9d5dfe7afccb3b7fdae8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8093470..9499afb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -110,6 +110,61 @@ typedef struct PartitionInfoData
 	PartitionRangeInfo	   *range;		/* range partition info */
 } PartitionInfoData;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo					PartitionKey executor state
+ *
+ *	pdesc					Info about immediate partitions (see
+ *							PartitionDescData)
+ *
+ *	index					If a partition ourselves, index in the parent's
+ *							partition array
+ *
+ *	num_leaf_partitions		Number of leaf partitions in the partition
+ *							tree rooted at this node
+ *
+ *	offset					0-based index of the first leaf partition
+ *							in the partition tree rooted at this node
+ *
+ *	downlink				Link to our leftmost child node (ie, corresponding
+ *							to first of our partitions that is itself
+ *							partitioned)
+ *
+ *	next					Link to the right sibling node on a given level
+ *							(ie, corresponding to the next partition on the same
+ *							level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
@@ -149,6 +204,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionCheckQual() */
 static List *generate_partition_check_qual(Relation rel);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -896,6 +955,53 @@ RelationGetPartitionCheckQual(Relation rel)
 	return generate_partition_check_qual(rel);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1495,6 +1601,106 @@ generate_partition_check_qual(Relation rel)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->parts[i]->oid;
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b2782f8..85384cb 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -30,6 +30,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -57,4 +58,8 @@ extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_check_qual_from_partbound(Relation rel, Relation parent,
 										   Node *bound);
 extern List *RelationGetPartitionCheckQual(Relation rel);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-2.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-2.patchDownload
From 320fd42db6bb2f6a122ce95a0d1cc0bd93b5c721 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  348 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   46 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 +++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 907 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9499afb..59f2127 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -208,6 +208,18 @@ static List *generate_partition_check_qual(Relation rel);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -236,6 +248,10 @@ static int32 partition_range_tuple_cmp(PartitionKey key,
 						   Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key,
 							PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple);
 
 /*
  * Partition key related functions
@@ -1617,7 +1633,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1701,6 +1717,270 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = copy_partition_key(rel->rd_partkey);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->parts[index]->oid, NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	int			i;
+
+	Assert(pdesc->nparts > 0);
+
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		int		j;
+
+		for (j = 0; j < pdesc->parts[i]->list->nvalues; j++)
+		{
+			if (isnull)
+			{
+				if (pdesc->parts[i]->list->nulls[j])
+					return i;
+				continue;
+			}
+
+			if (!pdesc->parts[i]->list->nulls[j] &&
+				partition_list_values_equal(key,
+											pdesc->parts[i]->list->values[j],
+											value))
+				return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+
+	return range_partition_bsearch(key, pdesc, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -2019,3 +2299,69 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * range_partition_bsearch
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = pdesc->nparts - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (pdesc->parts[idx]->range->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, pdesc->parts[idx]->range->upper))
+		{
+			if (pdesc->parts[idx]->range->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3f5c4e6..4963cb8 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1370,94 @@ BeginCopy(bool is_from,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1659,6 +1753,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1672,6 +1768,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2216,6 +2329,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2236,7 +2350,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2344,6 +2459,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2487,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2392,10 +2509,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2447,6 +2600,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2467,7 +2645,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2497,7 +2684,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2705,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2530,7 +2724,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e45a83d..5b4cec9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3d82658..2f22b3f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionCheckQual(resultRelationDesc);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..32f4031 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6152,6 +6153,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 70e7d4f..6ef45d5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1708,3 +1708,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..9f87f57 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -797,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 85384cb..14fd29e 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/relcache.h"
 
@@ -62,4 +64,9 @@ extern List *RelationGetPartitionCheckQual(Relation rel);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 31ca9ed..8943172 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-2.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-2.patchDownload
From 8b6f7276c5db879c1bf619952c7a1518048873d4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a393813..253eeb4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#17Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#15)
Re: Declarative partitioning - another take

On Fri, Aug 26, 2016 at 1:33 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

We do take a lock on the parent because we would be changing its partition
descriptor (relcache). I changed MergeAttributes() such that an
AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
parent is a partitioned table.

Hmm, that seems both good and bad. On the good side, as mentioned,
being able to rely on the partition descriptor not changing under us
makes this sort of thing much easier to reason about. On the bad
side, it isn't good for this feature to have worse concurrency than
regular inheritance. Not sure what to do about this.

If we need an AccessExclusiveLock on parent to add/remove a partition
(IOW, changing that child table's partitioning information), then do we
need to lock the individual partitions when reading partition's
information? I mean to ask why the simple syscache look-ups to get each
partition's bound wouldn't do.

Well, if X can't be changed without having an AccessExclusiveLock on
the parent, then an AccessShareLock on the parent is sufficient to
read X, right? Because those lock modes conflict.

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

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

#18Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#17)
Re: Declarative partitioning - another take

On 2016/08/29 20:53, Robert Haas wrote:

On Fri, Aug 26, 2016 at 1:33 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

We do take a lock on the parent because we would be changing its partition
descriptor (relcache). I changed MergeAttributes() such that an
AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
parent is a partitioned table.

Hmm, that seems both good and bad. On the good side, as mentioned,
being able to rely on the partition descriptor not changing under us
makes this sort of thing much easier to reason about. On the bad
side, it isn't good for this feature to have worse concurrency than
regular inheritance. Not sure what to do about this.

If we need an AccessExclusiveLock on parent to add/remove a partition
(IOW, changing that child table's partitioning information), then do we
need to lock the individual partitions when reading partition's
information? I mean to ask why the simple syscache look-ups to get each
partition's bound wouldn't do.

Well, if X can't be changed without having an AccessExclusiveLock on
the parent, then an AccessShareLock on the parent is sufficient to
read X, right? Because those lock modes conflict.

Yes. And hence we can proceed with performing partition elimination
before locking any of children. Lock on parent (AccessShareLock) will
prevent any of existing partitions to be removed and any new partitions to
be added because those operations require AccessExclusiveLock on the
parent. What I was trying to understand is why this would not be possible
with a design where partition bound is stored in the catalog as a property
of individual partitions instead of a design where we store collection of
partition bounds as a property of the parent.

Thanks,
Amit

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

#19Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#18)
Re: Declarative partitioning - another take

On Wed, Aug 31, 2016 at 12:37 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

If we need an AccessExclusiveLock on parent to add/remove a partition
(IOW, changing that child table's partitioning information), then do we
need to lock the individual partitions when reading partition's
information? I mean to ask why the simple syscache look-ups to get each
partition's bound wouldn't do.

Well, if X can't be changed without having an AccessExclusiveLock on
the parent, then an AccessShareLock on the parent is sufficient to
read X, right? Because those lock modes conflict.

Yes. And hence we can proceed with performing partition elimination
before locking any of children. Lock on parent (AccessShareLock) will
prevent any of existing partitions to be removed and any new partitions to
be added because those operations require AccessExclusiveLock on the
parent.

Agreed.

What I was trying to understand is why this would not be possible
with a design where partition bound is stored in the catalog as a property
of individual partitions instead of a design where we store collection of
partition bounds as a property of the parent.

From the point of view of feasibility, I don't think it matters very
much where the property is stored; it's the locking that is the key
thing. In other words, I think this *would* be possible if the
partition bound is stored as a property of individual partitions, as
long as it can't change without a lock on the parent.

However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.

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

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

#20Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#19)
Re: Declarative partitioning - another take

On 2016/08/31 16:17, Robert Haas wrote:

On Wed, Aug 31, 2016 at 12:37 PM, Amit Langote wrote:

What I was trying to understand is why this would not be possible
with a design where partition bound is stored in the catalog as a property
of individual partitions instead of a design where we store collection of
partition bounds as a property of the parent.

From the point of view of feasibility, I don't think it matters very
much where the property is stored; it's the locking that is the key
thing. In other words, I think this *would* be possible if the
partition bound is stored as a property of individual partitions, as
long as it can't change without a lock on the parent.

However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.

Seems worth trying. One point that bothers me a bit is how do we enforce
partition bound condition on individual partition basis. For example when
a row is inserted into a partition directly, we better check that it does
not fall outside the bounds and issue an error otherwise. With current
approach, we just look up a partition's bound from the catalog and gin up
a check constraint expression (and cache in relcache) to be enforced in
ExecConstraints(). With the new approach, I guess we would need to look
up the parent's partition descriptor. Note that the checking in
ExecConstraints() is turned off when routing a tuple from the parent.

Thanks,
Amit

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

#21Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#14)
Re: Declarative partitioning - another take

On 2016/08/25 16:15, Ashutosh Bapat wrote:

On Thu, Aug 25, 2016 at 12:22 PM, Amit Langote wrote:

b)
when accumulating append subpaths, do not flatten a subpath that is itself
an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
non-NULL partitioning info.Is the latter somehow necessary for
pairwise-join considerations?

I don't think you need to do anything in the path creation code for this.
As is it flattens all AppendPath hierarchies whether for partitioning or
inheritance or subqueries. We should leave it as it is.

I thought it would be convenient for pairwise join code to work with the
hierarchy intact even within the AppendPath tree. If it turns out to be
so, maybe that patch can take care of it.

I think I can manage to squeeze in (a) in the next version patch and will
also start working on (b), mainly the part about RelOptInfo getting some
partitioning info.

I am fine with b, where you would include some partitioning information in
RelOptInfo. But you don't need to do what you said in (b) above.

In a private conversation Robert Haas suggested a way slightly different
than what my patch for partition-wise join does. He suggested that the
partitioning schemes i.e strategy, number of partitions and bounds of the
partitioned elations involved in the query should be stored in PlannerInfo
in the form of a list. Each partitioning scheme is annotated with the
relids of the partitioned relations. RelOptInfo of the partitioned relation
will point to the partitioning scheme in PlannerInfo. Along-with that each
RelOptInfo will need to store partition keys for corresponding relation.
This simplifies matching the partitioning schemes of the joining relations.
Also it reduces the number of copies of partition bounds floating around as
we expect that a query will involve multiple partitioned tables following
similar partitioning schemes. May be you want to consider this idea while
working on (b).

So IIUC, a partitioned relation's (baserel or joinrel) RelOptInfo has only
the information about partition keys. They will be matched with query
restriction quals pruning away any unneeded partitions which happens
individually for each such parent baserel (within set_append_rel_size() I
suppose). Further, two joining relations are eligible to be considered
for pairwise joining if they have identical partition keys and query
equi-join quals match the same. The resulting joinrel will have the same
partition key (as either joining relation) and will have as many
partitions as there are in the intersection of sets of partitions of
joining rels (intersection proceeds by matching partition bounds).

"Partition scheme" structs go into a PlannerInfo list member, one
corresponding to each partitioned relation - baserel or joinrel, right?
As you say, each such struct has the following pieces of information:
strategy, num_partitions, bounds (and other auxiliary info). Before
make_one_rel() starts, the list has one for each partitioned baserel.
After make_one_rel() has formed baserel pathlists and before
make_rel_from_joinlist() is called, are the partition scheme structs of
processed baserels marked with some information about the pruning activity
that occurred so far? Then as we build successively higher levels of
joinrels, new entries will be made for those joinrels for which we added
pairwise join paths, with relids matching the corresponding joinrels.
Does that make sense?

Thanks,
Amit

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

#22Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#21)
Re: Declarative partitioning - another take

I don't think you need to do anything in the path creation code for this.
As is it flattens all AppendPath hierarchies whether for partitioning or
inheritance or subqueries. We should leave it as it is.

I thought it would be convenient for pairwise join code to work with the
hierarchy intact even within the AppendPath tree. If it turns out to be
so, maybe that patch can take care of it.

Partition-wise join work with RelOptInfos, so it's fine if the AppendPath
hierarchy is flattened out. We need the RelOptInfo hierarchy though.

I think I can manage to squeeze in (a) in the next version patch and

will

also start working on (b), mainly the part about RelOptInfo getting some
partitioning info.

I am fine with b, where you would include some partitioning information

in

RelOptInfo. But you don't need to do what you said in (b) above.

In a private conversation Robert Haas suggested a way slightly different
than what my patch for partition-wise join does. He suggested that the
partitioning schemes i.e strategy, number of partitions and bounds of the
partitioned elations involved in the query should be stored in

PlannerInfo

in the form of a list. Each partitioning scheme is annotated with the
relids of the partitioned relations. RelOptInfo of the partitioned

relation

will point to the partitioning scheme in PlannerInfo. Along-with that

each

RelOptInfo will need to store partition keys for corresponding relation.
This simplifies matching the partitioning schemes of the joining

relations.

Also it reduces the number of copies of partition bounds floating around

as

we expect that a query will involve multiple partitioned tables following
similar partitioning schemes. May be you want to consider this idea while
working on (b).

So IIUC, a partitioned relation's (baserel or joinrel) RelOptInfo has only
the information about partition keys. They will be matched with query
restriction quals pruning away any unneeded partitions which happens
individually for each such parent baserel (within set_append_rel_size() I
suppose). Further, two joining relations are eligible to be considered
for pairwise joining if they have identical partition keys and query
equi-join quals match the same. The resulting joinrel will have the same
partition key (as either joining relation) and will have as many
partitions as there are in the intersection of sets of partitions of
joining rels (intersection proceeds by matching partition bounds).

"Partition scheme" structs go into a PlannerInfo list member, one
corresponding to each partitioned relation - baserel or joinrel, right?

Multiple relations (base or join) can share Partition Scheme if they are
partitioned the same way. Each partition scheme also stores the relids of
the base relations partitioned by that scheme.

As you say, each such struct has the following pieces of information:
strategy, num_partitions, bounds (and other auxiliary info). Before
make_one_rel() starts, the list has one for each partitioned baserel.

After make_one_rel() has formed baserel pathlists and before

make_rel_from_joinlist() is called, are the partition scheme structs of
processed baserels marked with some information about the pruning activity
that occurred so far?

Right now pruned partitions are labelled as dummy rels (empty appent
paths). That's enough to detect a pruned partition. I haven't found a need
to label partitioning scheme with pruned partitions for partition-wise join.

Then as we build successively higher levels of
joinrels, new entries will be made for those joinrels for which we added
pairwise join paths, with relids matching the corresponding joinrels.
Does that make sense?

I don't think we will make any new partition scheme entry in PlannerInfo
after all the base relations have been considered. Partitionin-wise join
will pick the one suitable for the given join. But in case partition-wise
join needs to make new entries, I will take care of that in my patch.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#23Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#1)
Re: Declarative partitioning - another take

Here's something I observed with your set of patches posted in June. I have
not checked the latest set of patches. So, if it's something fixed, please
ignore the mail and sorry for me being lazy.

prt1 is partitioned table and it shows following information with \d+

regression=# \d+ prt1
Partitioned table "public.prt1"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Key: PARTITION BY RANGE (a)
Indexes:
"iprt1_a" btree (a)

Shouldn't we show all the partitions of this table and may be their ranges
of lists?

I found the partitions from EXPLAIN plan

regression=# explain verbose select * from prt1;
QUERY PLAN
-----------------------------------------------------------------------
Append (cost=0.00..6.00 rows=301 width=13)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
-> Seq Scan on public.prt1_p1 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p1.a, prt1_p1.b, prt1_p1.c
-> Seq Scan on public.prt1_p3 (cost=0.00..1.50 rows=50 width=13)
Output: prt1_p3.a, prt1_p3.b, prt1_p3.c
-> Seq Scan on public.prt1_p2 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p2.a, prt1_p2.b, prt1_p2.c
(9 rows)

Then did \d+ on each of those to find their ranges

regression=# \d+ prt1_p1
Table "public.prt1_p1"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (0) END (250)
Indexes:
"iprt1_p1_a" btree (a)

regression=# \d+ prt1_p2
Table "public.prt1_p2"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (250) END (500)
Indexes:
"iprt1_p2_a" btree (a)

regression=# \d+ prt1_p3
Table "public.prt1_p3"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (500) END (600)
Indexes:
"iprt1_p3_a" btree (a)

As you will observe that the table prt1 can not have any row with a < 0 and
a > 600. But when I execute

regression=# explain verbose select * from prt1 where a > 1000000;
QUERY PLAN
------------------------------------------------------------------
Append (cost=0.00..0.00 rows=1 width=40)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
Filter: (prt1.a > 1000000)
(4 rows)

it correctly excluded all the partitions, but did not exclude the parent
relation. I guess, we have enough information to exclude it. Probably, we
should add a check constraint on the parent which is OR of the check
constraints on all the partitions. So there are two problems here

1. \d+ doesn't show partitions - this is probably reported earlier, I don't
remember.
2. A combination of constraints on the partitions should be applicable to
the parent. We aren't doing that.

On Wed, Aug 10, 2016 at 4:39 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Hi,

Attached is the latest set of patches to implement declarative
partitioning. There is already a commitfest entry for the same:
https://commitfest.postgresql.org/10/611/

The old discussion is here:
/messages/by-id/55D3093C.5010800@lab.ntt.co.jp/

Attached patches are described below:

0001-Catalog-and-DDL-for-partition-key.patch
0002-psql-and-pg_dump-support-for-partitioned-tables.patch

These patches create the infrastructure and DDL for partitioned
tables.

In addition to a catalog for storing the partition key information, this
adds a new relkind to pg_class.h. PARTITION BY clause is added to CREATE
TABLE. Tables so created are RELKIND_PARTITIONED_REL relations which are
to be special in a number of ways, especially with regard to their
interactions with regular table inheritance features.

PARTITION BY RANGE ({ column_name | ( expression ) } [ opclass ] [, ...])
PARTITION BY LIST ({ column_name | ( expression ) } [ opclass ])

0003-Catalog-and-DDL-for-partition-bounds.patch
0004-psql-and-pg_dump-support-for-partitions.patch

These patches create the infrastructure and DDL for partitions.

Parent-child relationships of a partitioned table and its partitions are
managed behind-the-scenes with inheritance. That means there is a
pg_inherits entry and attributes, constraints, etc. are marked with
inheritance related information appropriately. However this case differs
from a regular inheritance relationship in a number of ways. While the
regular inheritance imposes certain restrictions on what elements a
child's schema is allowed to contain (both at creation time and
after-the-fact), the partitioning related code imposes further
restrictions. For example, while regular inheritance allows a child to
contain its own columns, the partitioning code disallows that. Stuff like
NO INHERIT marking on check constraints, ONLY are ignored by the the
partitioning code.

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

CREATE TABLE partition_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
[ PARTITION BY {RANGE | LIST} ( { column_name | ( expression ) } [ opclass
] [, ...] )

CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
SERVER server_name
[ OPTIONS ( option 'value' [, ... ] ) ]

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

ALTER TABLE parent DETACH PARTITION partition_name

partition_bound_spec is:

FOR VALUES { list_spec | range_spec }

list_spec in FOR VALUES is:

IN ( expression [, ...] )

range_spec in FOR VALUES is:

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

where lower-bound and upper-bound are:

{ ( expression [, ...] ) | UNBOUNDED }

expression can be a string literal, a numeric literal or NULL.

Note that the one can specify PARTITION BY when creating a partition
itself. That is to allow creating multi-level partitioned tables.

0005-Teach-a-few-places-to-use-partition-check-constraint.patch

A partition's bound implicitly constrains the values that are allowed in
the partition key of its rows. The same can be applied to partitions when
inserting data *directly* into them to make sure that only the correct
data is allowed in (if a tuple has been routed from the parent, that
becomes unnecessary). To that end, ExecConstraints() now includes the
above implicit check constraint in the list of constraints it enforces.

Further, to enable constraint based partition exclusion on partitioned
tables, the planner code includes in its list of constraints the above
implicitly defined constraints. This arrangement is temporary however and
will be rendered unnecessary when we implement special data structures and
algorithms within the planner in future versions of this patch to use
partition metadata more effectively for partition exclusion.

Note that the "constraints" referred to above are not some on-disk
structures but those generated internally on-the-fly when requested by a
caller.

0006-Introduce-a-PartitionTreeNode-data-structure.patch
0007-Tuple-routing-for-partitioned-tables.patch

These patches enable routing of tuples inserted into a partitioned table
to one of its leaf partitions. It applies to both COPY FROM and INSERT.
First of these patches introduces a data structure that provides a
convenient means for the tuple routing code to step down a partition tree
one level at a time. The second one modifies copy.c and executor to
implement actual tuple routing. When inserting into a partition, its row
constraints and triggers are applied. Note that the partition's
constraints also include the constraints defined on the parent. This
arrangements means however that the parent's triggers are not currently
applied.

Updates are handled like they are now for inheritance sets, however, if an
update makes a row change partition, an error will be thrown.

0008-Update-DDL-Partitioning-chapter.patch

This patch updates the partitioning section in the DDL chapter to reflect
the new methods made available for creating and managing partitioned table
and its partitions. Especially considering that it is no longer necessary
to define CHECK constraints and triggers/rules manually for constraint
exclusion and tuple routing, respectively.

TODO (in short term):
* Add more regression tests and docs
* Add PartitionOptInfo and use it to perform partition pruning more
effectively (the added infrastructure should also help pairwise joins
patch proposed by Ashutosh Bapat [1])
* Fix internal representation of list partition bounds to be more efficient

Thanks,
Amit

[1]
/messages/by-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXn
a-urjm_UY9BqXj%3DEaDTSA%40mail.gmail.com

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

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#24Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#23)
Re: Declarative partitioning - another take

On 2016/09/02 14:38, Ashutosh Bapat wrote:

Here's something I observed with your set of patches posted in June. I have
not checked the latest set of patches. So, if it's something fixed, please
ignore the mail and sorry for me being lazy.

prt1 is partitioned table and it shows following information with \d+

regression=# \d+ prt1
Partitioned table "public.prt1"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Key: PARTITION BY RANGE (a)
Indexes:
"iprt1_a" btree (a)

Shouldn't we show all the partitions of this table and may be their ranges
of lists?

Something I thought about as well. I will implement that.

I found the partitions from EXPLAIN plan

regression=# explain verbose select * from prt1;
QUERY PLAN
-----------------------------------------------------------------------
Append (cost=0.00..6.00 rows=301 width=13)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
-> Seq Scan on public.prt1_p1 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p1.a, prt1_p1.b, prt1_p1.c
-> Seq Scan on public.prt1_p3 (cost=0.00..1.50 rows=50 width=13)
Output: prt1_p3.a, prt1_p3.b, prt1_p3.c
-> Seq Scan on public.prt1_p2 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p2.a, prt1_p2.b, prt1_p2.c
(9 rows)

Then did \d+ on each of those to find their ranges

[ ... ]

As you will observe that the table prt1 can not have any row with a < 0 and
a > 600. But when I execute

regression=# explain verbose select * from prt1 where a > 1000000;
QUERY PLAN
------------------------------------------------------------------
Append (cost=0.00..0.00 rows=1 width=40)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
Filter: (prt1.a > 1000000)
(4 rows)

it correctly excluded all the partitions, but did not exclude the parent
relation. I guess, we have enough information to exclude it. Probably, we
should add a check constraint on the parent which is OR of the check
constraints on all the partitions. So there are two problems here

1. \d+ doesn't show partitions - this is probably reported earlier, I don't
remember.

You just did, :)

As I said I will implement that on lines of how inheritance children are
listed (with additional information ie, range or list).

2. A combination of constraints on the partitions should be applicable to
the parent. We aren't doing that.

How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
table, we can have get_relation_constraints() include a constant false
clause in the list of constraints returned for
relation_excluded_by_constraints() to process so that it is not included
in the append result by way of constraint exclusion. One more option is
to mark such rels dummy in set_rel_size().

Thanks,
Amit

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

#25Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#24)
Re: Declarative partitioning - another take

2. A combination of constraints on the partitions should be applicable to
the parent. We aren't doing that.

How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
table, we can have get_relation_constraints() include a constant false
clause in the list of constraints returned for
relation_excluded_by_constraints() to process so that it is not included
in the append result by way of constraint exclusion. One more option is
to mark such rels dummy in set_rel_size().

I am not complaining about having parent relation there. For the people who
are used to seeing the parent relation in the list of append relations, it
may be awkward. But +1 if we can do that. If we can't do that, we should at
least mark with an OR of all constraints on the partitions, so that
constraint exclusion can exclude it if there are conditions incompatible
with constraints. This is what would happen in inheritance case as well, if
there are constraints on the parent. In the above example, the parent table
would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
500) OR (a >= 500 or a < 600)). It will probably get excluded, if
constraint exclusion is smart enough to understand ORing.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#26Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#25)
Re: Declarative partitioning - another take

On 2016/09/02 15:22, Ashutosh Bapat wrote:

2. A combination of constraints on the partitions should be applicable to
the parent. We aren't doing that.

How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
table, we can have get_relation_constraints() include a constant false
clause in the list of constraints returned for
relation_excluded_by_constraints() to process so that it is not included
in the append result by way of constraint exclusion. One more option is
to mark such rels dummy in set_rel_size().

I am not complaining about having parent relation there. For the people who
are used to seeing the parent relation in the list of append relations, it
may be awkward. But +1 if we can do that. If we can't do that, we should at
least mark with an OR of all constraints on the partitions, so that
constraint exclusion can exclude it if there are conditions incompatible
with constraints. This is what would happen in inheritance case as well, if
there are constraints on the parent. In the above example, the parent table
would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
500) OR (a >= 500 or a < 600)). It will probably get excluded, if
constraint exclusion is smart enough to understand ORing.

I guess constraint exclusion would be (is) smart enough to handle that
correctly but why make it unnecessarily spend a *significant* amount of
time on doing the proof (when we *know* we can just skip it). Imagine how
long the OR list could get. By the way, my suggestion of just returning a
constant false clause also does not work - neither in case of a query with
restrictions (predicate has to be an OpExpr to go ahead with the proof
which constant false clause is not) nor in case of a query without
restrictions (proof is not performed at all). So, that one is useless.

Getting rid of the parent table in the append list by other means may be a
way to go. We know that the table is empty and safe to just drop.

Thanks,
Amit

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

#27Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#26)
Re: Declarative partitioning - another take

On Fri, Sep 2, 2016 at 12:23 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

On 2016/09/02 15:22, Ashutosh Bapat wrote:

2. A combination of constraints on the partitions should be applicable

to

the parent. We aren't doing that.

How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
table, we can have get_relation_constraints() include a constant false
clause in the list of constraints returned for
relation_excluded_by_constraints() to process so that it is not

included

in the append result by way of constraint exclusion. One more option is
to mark such rels dummy in set_rel_size().

I am not complaining about having parent relation there. For the people

who

are used to seeing the parent relation in the list of append relations,

it

may be awkward. But +1 if we can do that. If we can't do that, we should

at

least mark with an OR of all constraints on the partitions, so that
constraint exclusion can exclude it if there are conditions incompatible
with constraints. This is what would happen in inheritance case as well,

if

there are constraints on the parent. In the above example, the parent

table

would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
500) OR (a >= 500 or a < 600)). It will probably get excluded, if
constraint exclusion is smart enough to understand ORing.

I guess constraint exclusion would be (is) smart enough to handle that
correctly but why make it unnecessarily spend a *significant* amount of
time on doing the proof (when we *know* we can just skip it). Imagine how
long the OR list could get. By the way, my suggestion of just returning a
constant false clause also does not work - neither in case of a query with
restrictions (predicate has to be an OpExpr to go ahead with the proof
which constant false clause is not) nor in case of a query without
restrictions (proof is not performed at all). So, that one is useless.

Huh!

Getting rid of the parent table in the append list by other means may be a
way to go. We know that the table is empty and safe to just drop.

Ok. Does a constraint (1 = 2) or something like that which is obviously
false, help?

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#28Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#27)
Re: Declarative partitioning - another take

On 2016/09/02 15:57, Ashutosh Bapat wrote:

On Fri, Sep 2, 2016 at 12:23 PM, Amit Langote wrote:

Getting rid of the parent table in the append list by other means may be a
way to go. We know that the table is empty and safe to just drop.

Ok. Does a constraint (1 = 2) or something like that which is obviously
false, help?

No, anything like that would get reduced to a constant false clause by
eval_const_expressions() processing.

Thanks,
Amit

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

#29Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#16)
1 attachment(s)
Re: Declarative partitioning - another take

I found a server crash while running make check in regress folder. with
this set of patches. Problem is RelationBuildPartitionKey() partexprsrc may
be used uninitialized. Initializing it with NIL fixes the crash. Here's
patch to fix it. Came up with the fix after discussion with Amit.

On Fri, Aug 26, 2016 at 1:45 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Sorry it took me a while to reply. Attached updated patches including the
review comments on 0001 at [1].

On 2016/08/17 3:54, Robert Haas wrote:

On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

0002-psql-and-pg_dump-support-for-partitioned-tables.patch

+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')

Version check is redundant, right?

Yep, fixed.

+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers
+--------+---------+-----------
+ a      | integer |
+ b      | integer |
+Partition Key: PARTITION BY RANGE (((a + b)))

I understand that it's probably difficult not to end up with two sets
of parentheses here, but can we avoid ending up with three sets?

Fixed. This code was copy-pasted from the index code which has other
considerations for adding surrounding parentheses which don't apply to the
partition key code.

Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
pg_dump can add that part back. Then this could say:

Partition Key: RANGE ((a + b))

...which seems a good deal more natural than what you have now.

Agreed, so done that way.

0003-Catalog-and-DDL-for-partition-bounds.patch

Partition DDL includes both a way to create new partition and "attach"

an

existing table as a partition of parent partitioned table. Attempt to
drop a partition using DROP TABLE causes an error. Instead a partition
needs first to be "detached" from parent partitioned table. On the

other

hand, dropping the parent drops all the partitions if CASCADE is

specified.

Hmm, I don't think I like this. Why should it be necessary to detach
a partition before dropping it? That seems like an unnecessary step.

I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.

[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
IMMEDIATE ]
+
</synopsis>

Unnecessary hunk.

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

Sentence fragment.

Fixed.

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

This is another restriction I don't understand. Why can't I attach a
partitioned table?

I removed this restriction.

ATExecAttachPartition() adds a AT work queue entry for the table being
attached to perform a heap scan in the rewrite phase. The scan is done
for checking that no row violates the partition boundary condition. When
attaching a partitioned table as partition, multiple AT work queue entries
are now added - one for each leaf partition of the table being attached.

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

Ugh, why? I think this should work exactly the same way for
partitioned tables that it does for any other inheritance hierarchy.
Sure, you'll get no rows, but who cares?

Agreed, done that way.

+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES START ('2016-07-01') END
('2016-08-01');
+    SERVER server_07;

Extra semicolon?

Fixed.

+ A partition cannot have columns other than those inherited from

the

+ parent. That includes the <structfield>oid</> column, which can

be

I think experience suggests that this is a good restriction, but then
why does the syntax synopsis indicate that PARTITION BY can be
specified along with column definitions? Similarly for CREATE FOREIGN
TABLE.

The syntax synopsis of CREATE TABLE ... PARTITION OF indicates that a list
of column WITH OPTION and/or table_constraint can be specified. It does
not allow column definitions.

In this case, inherited columns will be listed in the PARTITION BY clause.

Do you mean that the CREATE TABLE ... PARTITION OF syntax should allow
column definitions just like INHERITS does and unlike regular inheritance,
throw error if columns other than those to be merged are found?

+ When specifying for a table being created as partition, one needs

to

+ use column names from the parent table as part of the key.

This is not very clear.

Sentence removed because it may be clear from the context that inherited
columns are to be used for the partition key.

-       /* 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;

It might be right to disallow NO INHERIT in this case, but I don't
think it can be right to just silently ignore it.

OK, I changed this to instead throw an error if a NO INHERIT check
constraint is added to a partitioned table or a partition.

+ * Not flushed from the cache by RelationClearRelation() unless changed

because

+ * of addition or removal of partitions.

This seems unlikely to be safe, unless I'm missing something.

Like TupleDesc, a table's PartitionDesc is preserved across relcache
rebuilds. PartitionDesc consists of arrays of OIDs and partition bounds
for a table's immediate partitions. It can only change by adding/removing
partitions to/from the table which requires an exclusive lock on it.
Since this data can grow arbitrarily big it seemed better to not have to
copy it around, so a direct pointer to the relcache field (rd_partdesc) is
given to callers. relcache rebuilds that do not logically change
PartitionDesc leave it intact so that some user of it is not left with a
dangling pointer. Am I missing something?

+       form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+       systable_endscan(scan);
+       heap_close(catalogRelation, AccessShareLock);
+
+       return form->inhparent;

This is unsafe. After systable_endscan, it is no longer OK to access
form->inhparent.

Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.

There should probably be a note in the function header comment that it
is unsafe to use this for an inheritance child that is not a
partition, because there could be more than one parent in that case.
Or maybe the whole idea of this function just isn't very sound...

Fixed unsafe coding and added a comment to the function saying it should
be called on tables known to be partitions.

+static List *
+get_partitions(Oid relid, int lockmode)
+{
+       return find_inheritance_children(relid, lockmode);
+}

What's the point? If we're going to have a wrapper here at all, then
shouldn't it have a name that matches the existing convention - e.g.
find_partitions() or find_child_partitions()? But I think you might
as well just use find_inheritance_children() directly.

OK, changed to just use find_inheritance_children() directly.

+                * Happens when we have created the pg_inherits entry
but not the
+                * pg_partition entry yet.

Why do we ever allow the flow of control to reach this point while we
are in such an intermediate state?

Fixed to prevent this from happening.

+free_partition_info(PartitionInfoData **p, int num)

Seems very error-prone. Isn't this why MemoryContextReset was invented?

Got rid of this function.

+relid_is_partition(Oid relid)
+{
+       return SearchSysCacheExists1(PARTRELID,

ObjectIdGetDatum(relid));

+}

This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...

OK, I got rid of the pg_partition catalog and added a pg_class attribute
for storing partition bound. Because the newly added attribute is a
pg_node_tree and hence not readily accessible without a heap_getattr()
call, I added a boolean relispartition as well. Now all the
relid_is_partition() calls have been replaced by checks using
relation->rd_rel->relispartition.

/*
+ * 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)));
+       }

Why? This seems like an entirely arbitrary prohibition. If inserts
aren't supported, then they'll fail at runtime. Same for updates or
deletes, for which you have not added checks. I think you should just
remove this.

OK, removed this check.

Instead, ExecInitModifyTable()/BeginCopy() call CheckValidResultRel() for
every leaf partition to check that they are ready for CMD_INSERT.

+               /* Force inheritance recursion, if partitioned table. */
+               if (recurse || relid_is_partitioned(myrelid))

Disagree with this, too. There's no reason for partitioned tables to
be special in this way. Similarly, disagree with all of the places
that do something similar.

Removed the forced recursion bit here and a few other places.

-                               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")));

You have a few of these; they cause problems for translators, because
different languages have different word ordering. Repeat the entire
message instead: is_attach_partition ? "column \"%s\" in source table
must be marked NOT NULL" : "column \"%s\" in child table must be
marked NOT NULL".

Changed so that the entire message is repeated.

+-- XXX add ownership tests

So do that. :-)

Done, sorry about that.

+ERROR:  column "b" is not null in parent
+HINT:  Please drop not null in the parent instead

Hmm. That hint doesn't seem like project style, and I'm not sure
that it really makes sense to issue such a hint anyway. Who knows
whether that is the right thing to do? I think you should somehow be
complaining about the fact that this is a partition, rather than
complaining about the fact that the column is NOT NULL in the parent.
Are we insisting that the flags match exactly, or only that the child
may not allow nulls unless the parent does?

I think the latter. It is assumed that all the parent's constraints are
present in a child table, because in ExecInsert()/CopyFrom() we perform
ExecConstraints() using the child relation even if the actual insert was
on the parent. Also, if an inherited NOT NULL constraint on a child's
column is dropped irrespective of parent's, selecting a parent's NOT NULL
column might return nulls from the child table that no longer has the
constraint.

I recently came across a related proposal whereby dropping *inherited* NOT
NULL from child tables will be prevented. Problems in letting it be be
dropped are mentioned here:

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

That proposal is probably being reworked such that NOT NULL constraints
get a pg_constraint entry with proper accounting of inheritance count.

+ERROR: new partition's list of values overlaps with partition
"lpart1" of "list_parted"

Maybe just:

ERROR: partitions must not overlap
-or-
ERROR: partition "%s" would overlap partition "%s"

OK, I changed to the second message.

As before, this is just an initial read-through, so apologies for
whatever I may have missed.

Thanks a lot for the review.

By the way, I am still working on the following items and will be included
in the next version of the patch.

* Fix internal representation of list partition bounds to be more efficient
* Add PartitionOptInfo

As mentioned in [2], I have refactored the inheritance expansion code
within optimizer so that a partitioned table's inheritance hierarchy is
preserved in resulting AppendRelInfos (patch 0005). One immediate benefit
of that is that if constraint exclusion determines that an intermediate
partition is to be excluded then all partitions underneath it are excluded
automatically. With the current flattened model, all partitions in that
subtree would have been processed and excluded one-by-one.

Regards,
Amit

[1]
/messages/by-id/CA+TgmoZ008qTgd_Qg6_
oZb3i0mOYrS6MdhncwgcqPKahixjarg%40mail.gmail.com
[2]
/messages/by-id/f2a9592a-17e9-4c6a-
e021-03b802195ce7%40lab.ntt.co.jp

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

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

Attachments:

fix_server_crash.patchtext/x-patch; charset=US-ASCII; name=fix_server_crash.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 59f2127..f17ac29 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -281,7 +281,7 @@ RelationBuildPartitionKey(Relation relation)
 	oidvector	   *opclass;
 	KeyTypeCollInfo *tcinfo;
 	ListCell	   *partexprbin_item;
-	List		   *partexprsrc;
+	List		   *partexprsrc = NIL;
 	ListCell	   *partexprsrc_item;
 	Datum			datum;
 	MemoryContext	partkeycxt,
#30Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#29)
9 attachment(s)
Re: Declarative partitioning - another take

Hi,

On 2016/09/06 16:13, Ashutosh Bapat wrote:

I found a server crash while running make check in regress folder. with
this set of patches. Problem is RelationBuildPartitionKey() partexprsrc may
be used uninitialized. Initializing it with NIL fixes the crash. Here's
patch to fix it. Came up with the fix after discussion with Amit.

Thanks for the report. Here is a rebased version of the patches including
you fix (no significant changes from those posted on Aug 26).

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-3.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-3.patchDownload
From 2d3c5684761bfd58c7d16f16f89bd93c0afa8bb9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                    |  102 +++++++-
 doc/src/sgml/ref/create_table.sgml            |   55 ++++
 src/backend/access/common/reloptions.c        |    2 +
 src/backend/catalog/Makefile                  |    6 +-
 src/backend/catalog/aclchk.c                  |    2 +
 src/backend/catalog/dependency.c              |    2 +
 src/backend/catalog/heap.c                    |   27 ++-
 src/backend/catalog/objectaddress.c           |    5 +-
 src/backend/catalog/partition.c               |  394 +++++++++++++++++++++++++
 src/backend/catalog/pg_depend.c               |    3 +
 src/backend/catalog/pg_partitioned_table.c    |  172 +++++++++++
 src/backend/commands/analyze.c                |    2 +
 src/backend/commands/copy.c                   |    6 +
 src/backend/commands/indexcmds.c              |    7 +-
 src/backend/commands/lockcmds.c               |    2 +-
 src/backend/commands/policy.c                 |    2 +-
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/sequence.c               |    1 +
 src/backend/commands/tablecmds.c              |  384 ++++++++++++++++++++++++-
 src/backend/commands/trigger.c                |    7 +-
 src/backend/commands/vacuum.c                 |    1 +
 src/backend/executor/execMain.c               |    2 +
 src/backend/executor/nodeModifyTable.c        |    1 +
 src/backend/nodes/copyfuncs.c                 |   33 ++
 src/backend/nodes/equalfuncs.c                |   28 ++
 src/backend/nodes/outfuncs.c                  |   26 ++
 src/backend/parser/gram.y                     |  110 ++++++--
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_expr.c               |    5 +
 src/backend/parser/parse_utilcmd.c            |   69 +++++
 src/backend/rewrite/rewriteDefine.c           |    1 +
 src/backend/rewrite/rewriteHandler.c          |    1 +
 src/backend/tcop/utility.c                    |    5 +-
 src/backend/utils/cache/relcache.c            |   19 ++-
 src/backend/utils/cache/syscache.c            |   12 +
 src/include/catalog/dependency.h              |    8 +-
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/partition.h               |   35 +++
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_partitioned_table.h    |   69 +++++
 src/include/catalog/pg_partitioned_table_fn.h |   29 ++
 src/include/commands/defrem.h                 |    2 +
 src/include/nodes/nodes.h                     |    2 +
 src/include/nodes/parsenodes.h                |   35 +++
 src/include/parser/kwlist.h                   |    1 +
 src/include/parser/parse_node.h               |    3 +-
 src/include/pg_config_manual.h                |    5 +
 src/include/utils/rel.h                       |    9 +
 src/include/utils/syscache.h                  |    1 +
 src/test/regress/expected/alter_table.out     |   46 +++
 src/test/regress/expected/create_table.out    |  155 ++++++++++
 src/test/regress/expected/sanity_check.out    |    1 +
 src/test/regress/sql/alter_table.sql          |   34 +++
 src/test/regress/sql/create_table.sql         |  133 +++++++++
 54 files changed, 2033 insertions(+), 45 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/backend/catalog/pg_partitioned_table.c
 create mode 100644 src/include/catalog/partition.h
 create mode 100644 src/include/catalog/pg_partitioned_table.h
 create mode 100644 src/include/catalog/pg_partitioned_table_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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>partedrelid</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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partkey</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns and/or expressions in the partition key and partition
+      rules associated with the partitions.
+     </para>
+
+     <para>
+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ba1f3aa..09e29d1 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_type.o storage.o toasting.o pg_partitioned_table.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_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..679f1c7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
 					 getObjectDescription(object));
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				subflags = 0;	/* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
+	/* Remove NO INHERIT flag if rel is a partitioned table */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
 	/*
 	 * Create the Check Constraint
 	 */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.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_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+	Oid		*typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+	char	  **partcolnames;	/* partition key column names */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+								KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	int2vector	   *partattrs;
+	oidvector	   *opclass;
+	KeyTypeCollInfo *tcinfo;
+	ListCell	   *partexprbin_item;
+	List		   *partexprsrc = NIL;
+	ListCell	   *partexprsrc_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	/* Allocate in the supposedly short-lived working context */
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/* Open the catalog for its tuple descriptor */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	partattrs = (int2vector *) DatumGetPointer(datum);
+
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprbin,
+						 RelationGetDescr(catalog),
+						 &isnull);
+
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+
+		/* We should have a partexprsrc as well */
+		datum = heap_getattr(tuple,
+							 Anum_pg_partitioned_table_partexprsrc,
+							 RelationGetDescr(catalog),
+							 &isnull);
+		Assert(!isnull);
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+		partexprsrc = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+	tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+	tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather partition column names (simple C strings) */
+	key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+	/* Copy partattrs and fill other per-attribute info */
+	partexprbin_item = list_head(key->partexprs);
+	partexprsrc_item = list_head(partexprsrc);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		HeapTuple		tuple;
+		AttrNumber		attno;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		key->partattrs[i] = attno = partattrs->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+			tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+			tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+		}
+		get_typlenbyvalalign(tcinfo->typid[i],
+							 &tcinfo->typlen[i],
+							 &tcinfo->typbyval[i],
+							 &tcinfo->typalign[i]);
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+
+		/* Collect atttribute names */
+		if (key->partattrs[i] != 0)
+			key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+															key->partattrs[i]);
+		else
+		{
+			Value *str = lfirst(partexprsrc_item);
+			key->partcolnames[i] = pstrdup(str->val.str);
+			partexprsrc_item = lnext(partexprsrc_item);
+		}
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+	return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				i;
+
+	newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *)
+							palloc0(newkey->partnatts * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs,
+							newkey->partnatts * sizeof(AttrNumber));
+
+	newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *)
+							palloc0(newkey->partnatts * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+	newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+											 fromkey->tcinfo);
+	newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+	for (i = 0; i < newkey->partnatts; i++)
+		newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+	return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+	KeyTypeCollInfo   *result = (KeyTypeCollInfo *)
+								palloc0(sizeof(KeyTypeCollInfo));
+
+	result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+	result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+	memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+	result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+	memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+	result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+	result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+	result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+	return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
 
+	if (behavior == DEPENDENCY_IGNORE)
+		return;					/* nothing to do */
+
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
 
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..45ec347
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ *	  routines to support manipulation of the pg_partitioned_table 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_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprbin,
+				  List *partexprsrc,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprbinDatum;
+	Datum		partexprsrcDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/*
+	 * Copy the partition key, opclass info into arrays (should we
+	 * make the caller pass them like this to start with?)
+	 */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprbin)
+	{
+		char       *exprbinString;
+		char       *exprsrcString;
+
+		exprbinString = nodeToString(partexprbin);
+		exprsrcString = nodeToString(partexprsrc);
+		partexprbinDatum = CStringGetTextDatum(exprbinString);
+		partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+		pfree(exprbinString);
+		pfree(exprsrcString);
+	}
+	else
+		partexprbinDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprbinDatum)
+	{
+		nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+		nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+	}
+
+	values[Anum_pg_partitioned_table_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+	values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprbin)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprbin,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_IGNORE);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 5947e72..be71334 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1716,6 +1716,12 @@ BeginCopyTo(Relation rel,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..e716032 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
 	bool		concurrent;
 };
 
+/* for find_attr_reference_walker */
+typedef struct
+{
+	AttrNumber	attnum;
+} find_attr_reference_context;
+
 /* Alter table target-type flags for ATSimplePermissions */
 #define		ATT_TABLE				0x0001
 #define		ATT_VIEW				0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass);
 
 
 /* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partby)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprbin = NIL;
+		List		   *partexprsrc = NIL;
+
+		stmt->partby = transformPartitionBy(rel, stmt->partby);
+		ComputePartitionAttrs(rel, stmt->partby->partParams,
+							  partattrs, &partexprbin, &partexprsrc,
+							  partopclass);
+
+		partnatts = list_length(stmt->partby->partParams);
+		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+						  partattrs, partexprbin, partexprsrc, partopclass);
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
+	 * RELKIND_RELATION while the relation is actually a partitioned table.
+	 */
+	if (classform->relkind != relkind &&
+				(relkind == RELKIND_RELATION &&
+					classform->relkind != RELKIND_PARTITIONED_TABLE))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var		   *variable = (Var *) node;
+		AttrNumber	attnum = variable->varattno;
+
+		if (attnum == context->attnum)
+			return true;
+	}
+
+	return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber partatt = get_partition_col_attnum(key, i);
+
+		if(partatt != 0)
+		{
+			*is_expr = false;
+			if (attnum == partatt)
+				return true;
+		}
+		else
+		{
+			find_attr_reference_context context;
+
+			*is_expr = true;
+			context.attnum = attnum;
+			if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+				return true;
+
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+	PartitionBy	   *partby;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	partby = (PartitionBy *) makeNode(PartitionBy);
+
+	partby->strategy = partitionby->strategy;
+	partby->location = partitionby->location;
+	partby->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+
+			/*
+			 * transformExpr() should have already rejected subqueries,
+			 * aggregates, and window functions, based on the EXPR_KIND_ for
+			 * a partition key expression.
+			 *
+			 * Also reject expressions returning sets; this is for consistency
+			 * DefineRelation() will make more checks.
+			 */
+			if (expression_returns_set(pelem->expr))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key expression cannot return a set"),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		partby->partParams = lappend(partby->partParams, pelem);
+	}
+
+	return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			if (IsA(expr, CollateExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot use COLLATE in partition key expression")));
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				char   *exprsrc;
+
+				partattrs[attn] = 0; /* marks expression */
+				*partexprbin = lappend(*partexprbin, expr);
+
+				/*
+				 * transformExpr() should have already rejected subqueries,
+				 * aggregates, and window functions, based on the EXPR_KIND_
+				 * for a partition key expression.
+				 */
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use a constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				exprsrc = deparse_expression(expr,
+							deparse_context_for(RelationGetRelationName(rel),
+												RelationGetRelid(rel)),
+									   false, false);
+				*partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+			}
+		}
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify an existing btree operator class or define one for the type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1877fb4..5668078 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3016,6 +3016,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4171,6 +4172,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+	PartitionBy *newnode = makeNode(PartitionBy);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5085,6 +5112,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionBy:
+			retval = _copyPartitionBy(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 448e1a9..23b7d31 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2630,6 +2631,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3383,6 +3405,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionBy:
+			retval = _equalPartitionBy(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 29b7712..8ad9781 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3279,6 +3280,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3863,6 +3884,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionBy:
+				_outPartitionBy(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cb5cfc4..3d5cde9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partby>		PartitionBy OptPartitionBy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partby = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partby = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partby = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partby = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13768,6 +13841,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * 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 e98fad0..819d403 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partby != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partby)
+	{
+		int		partnatts = list_length(stmt->partby->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -582,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -591,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -673,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -683,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -693,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -707,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -759,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2517,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac64135..71d48df 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partby != NULL
+													? RELKIND_PARTITIONED_TABLE
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..fb540e1 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partedrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
  * created only during initdb.  The fields for the dependent object
  * contain zeroes.
  *
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question.  However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
  * Other dependency flavors may be needed in future.
  */
 
@@ -77,7 +82,8 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
+	DEPENDENCY_IGNORE = 'g'
 } DependencyType;
 
 /*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..ef50f85 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partedrelid_index, 3351, on pg_partitioned_table using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* 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
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+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);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9a0f6f4
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partedrelid;	/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprbin;	/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+	pg_node_tree	partexprsrc
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partedrelid	1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partexprbin	6
+#define Anum_pg_partitioned_table_partexprsrc	7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprbin,
+					List *partexprsrc,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif   /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..9fe31ce 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionBy,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..2b77579 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -697,6 +697,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionBy
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionBy;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1751,6 +1785,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR:  cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-3.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-3.patchDownload
From ed52e9fef8fc5176e3fe26c9a6a12ff98b39802b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relationId = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relId), relId);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relId, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relId, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 019f75a..5e5c370 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-3.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-3.patchDownload
From 24cfacb3c2fbcf4e081f611e5ea41323e00f35ec Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 +++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/heap.c                 |   36 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1419 +++++++++++++++++++++++++++-
 src/backend/catalog/pg_partitioned_table.c |    2 -
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/lockcmds.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  773 +++++++++++++---
 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         |  331 +++++++-
 src/backend/utils/cache/relcache.c         |   82 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   25 +
 src/include/catalog/pg_class.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/test/regress/expected/alter_table.out  |  211 ++++
 src/test/regress/expected/create_table.out |  188 ++++
 src/test/regress/sql/alter_table.sql       |  185 ++++
 src/test/regress/sql/create_table.sql      |  138 +++
 35 files changed, 3969 insertions(+), 165 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1229,6 +1311,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..007782c 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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..1613218 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"
@@ -90,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -770,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -808,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -818,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -851,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -924,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1033,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1268,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2055,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
-	/* Remove NO INHERIT flag if rel is a partitioned table */
+	/* Remove NO INHERIT flag if rel is a partitioned table or a partition */
 	if (is_no_inherit &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2475,7 +2495,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..6d8759c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * partition.c
- *        Partitioning related utility functions.
+ *        Partitioning related data structures and functions.
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
 #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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/ruleutils.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /* Type and collation information for partition key columns */
@@ -69,11 +77,107 @@ 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);
 
+/* Support RelationBuildPartitionDesc() */
+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
  */
@@ -392,3 +496,1316 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
 
 	return result;
 }
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List		   *partoids;
+	ListCell	   *cell;
+	int				i,
+					nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	PartitionInfo  *parts;
+	MemoryContext	oldcxt;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Collect partition info from the catalogs */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+	parts = (PartitionInfoData **)
+							palloc0(nparts * sizeof(PartitionInfoData *));
+
+	i = 0;
+	foreach(cell, partoids)
+	{
+		Oid 		partrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *bound;
+
+		tuple = SearchSysCache1(RELOID, partrelid);
+		Assert(HeapTupleIsValid(tuple));
+
+		parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+		parts[i]->oid = partrelid;
+		datum = SysCacheGetAttr(RELOID, tuple,
+							Anum_pg_class_relpartbound,
+							&isnull);
+		Assert(!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++;
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (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;
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->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(char *relname, 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("partition \"%s\" would overlap partition \"%s\"",
+							relname, get_rel_name(with)),
+					 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("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (range_overlaps_existing_partition(key, range, pdesc, &with))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(with)),
+						 parser_errposition(pstate, range->location)));
+			break;
+		}
+	}
+
+	heap_close(parent, AccessShareLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * 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 (!rel->rd_rel->relispartition)
+		return NIL;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* Nope, so generate. */
+	return generate_partition_check_qual(rel);
+}
+
+/* Module-local functions */
+
+/*
+ * 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
+		{
+			/*
+			 * Determine operator inclusivity to use from the partition bound
+			 * spec (lowerinc or upperinc).
+			 */
+
+			/* Build leftop ge/gt lower_val */
+			strategy = 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 < 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 partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_check_qual(Relation rel)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_check = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/*
+	 * Generate executable expression using the information in
+	 * ListPartitionSpec and RangePartitionSpec nodes.
+	 */
+
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)), NoLock);
+	my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+	/*
+	 * If parent is also a partition, add its expressions to the list.
+	 */
+	if (parent->rd_rel->relispartition)
+	{
+		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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index 45ec347..5537ac2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
 										RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
 										DEPENDENCY_IGNORE);
-	/* Tell world about the key */
-	CacheInvalidateRelcache(rel);
 
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 	heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e716032..0805746 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 3bb5933..3cc45ef 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,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 */
+	List	   *partition_quals; /* partition check quals 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 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprbin, List **partexprsrc,
 					  Oid *partopclass);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
 	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1452,7 +1494,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1497,8 +1540,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 			/*
 			 * Typed table column option that does not belong to a column from
-			 * the type.  This works because the columns from the type come
-			 * first in the list.
+			 * the type or the partition parent.  This works because the columns
+			 * from the type or the partition parent come first in the list.
 			 */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
 	{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2581,11 +2669,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2827,8 +2916,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3494,7 +3597,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3743,6 +3852,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);
@@ -3928,7 +4043,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4008,6 +4123,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4072,6 +4188,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4386,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4589,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4780,6 +4912,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5302,6 +5439,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5989,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5924,9 +6086,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6479,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7877,7 +8043,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);
@@ -7936,8 +8104,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8179,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10176,6 +10351,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10368,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;
 
@@ -10238,37 +10413,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10452,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);
 
@@ -10317,16 +10529,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;
 }
 
 /*
@@ -10377,7 +10581,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
@@ -10395,12 +10599,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10630,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10440,8 +10652,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10456,7 +10670,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10470,7 +10686,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.
@@ -10489,10 +10705,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10539,7 +10759,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10547,7 +10769,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10558,6 +10782,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10571,7 +10796,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10582,6 +10809,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10595,13 +10862,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];
@@ -10610,19 +10875,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10888,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);
 
@@ -10653,11 +10909,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
@@ -10666,7 +10931,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)))
@@ -10728,7 +10993,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);
 
@@ -10759,7 +11024,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)
@@ -10771,30 +11036,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;
 }
 
 /*
@@ -12431,3 +12686,271 @@ ComputePartitionAttrs(Relation rel, 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,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals,
+					   *parent_quals = RelationGetPartitionCheckQual(rel);
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_check_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5668078..8dea2bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3016,6 +3017,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);
@@ -4198,6 +4200,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
  * ****************************************************************
@@ -5118,6 +5157,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 23b7d31..40ad793 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,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);
@@ -2374,6 +2375,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);
@@ -2652,6 +2654,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
  */
@@ -3411,6 +3444,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 8ad9781..3b8fa6b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,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);
@@ -2586,6 +2587,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);
@@ -3301,6 +3303,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'
@@ -3890,6 +3911,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 6f9a81e..466c0f2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2264,6 +2264,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
@@ -2495,6 +2524,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 3d5cde9..b6cce2c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,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
@@ -601,7 +611,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11135,6 +11334,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;
@@ -13734,6 +13934,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13780,6 +13981,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13822,6 +14024,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 819d403..563a41c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,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);
 
 
 /*
@@ -231,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +258,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")));
@@ -359,6 +371,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);
@@ -898,6 +911,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;
@@ -1117,6 +1131,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;
@@ -2586,6 +2601,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2674,6 +2690,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;
@@ -3038,3 +3070,300 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	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_partition_col_name(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_partition_col_name(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_partition_col_name(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 8cbd6e7..ac9ec92 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);
 
 
 /*
@@ -925,6 +927,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
@@ -1052,13 +1085,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2055,6 +2093,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2203,11 +2245,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2218,6 +2261,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);
@@ -2248,6 +2292,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
@@ -2303,6 +2350,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 */
@@ -3529,6 +3583,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5052,6 +5120,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..b2782f8 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);
 
@@ -32,4 +45,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
 extern int32 get_partition_col_typmod(PartitionKey key, int col);
 extern char *get_partition_col_name(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(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+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_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..96bea6b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,214 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_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);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
 Partition Key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..bc955e0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,188 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- 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

0004-psql-and-pg_dump-support-for-partitions-3.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-3.patchDownload
From 6739e22a37e3066447125205ae01a7e2957c78cf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   46 ++++++++++++-
 src/test/regress/expected/create_table.out |   23 +++++++
 src/test/regress/sql/create_table.sql      |    6 ++
 7 files changed, 340 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 77ce807..5d90413 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c418ba7..626fa00 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6125,6 +6125,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15302,6 +15359,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15330,8 +15398,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15360,7 +15431,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15425,15 +15497,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15603,6 +15682,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 10d924a..9554f5e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,15 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
+		/* print child tables (exclude, if parent is a partitioned table) */
 		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2b12276..5162c82 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,29 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index c38312e..b29ac79 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,12 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-3.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-3.patchDownload
From 7543ccfdae5505916cbe508a59d322606b7c78c4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
 src/backend/optimizer/prep/prepunion.c |  278 ++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 2 files changed, 204 insertions(+), 83 deletions(-)

diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..592214b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/* Do not flatten the inheritance hierarchy if partitioned table */
+	if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+
+	/* Otherwise, OK to add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (childOID != parentOID)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
-		 */
-		if (oldrc)
-		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
-		}
-
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-3.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-3.patchDownload
From f633b3e397db99cc887f6b017856d3c6bc555b43 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 ++++++++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 ++++++++
 src/include/nodes/execnodes.h          |    4 ++
 src/test/regress/expected/insert.out   |   76 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/update.out   |   27 +++++++++++
 src/test/regress/sql/insert.sql        |   56 +++++++++++++++++++++++
 src/test/regress/sql/update.sql        |   21 +++++++++
 9 files changed, 280 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be71334..ba5bf23 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2460,7 +2460,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..3d82658 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..70e7d4f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionCheckQual(relation);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e28477d..e35da66 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-3.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-3.patchDownload
From 3ed8417779e5bad7d9d83223d23471f07b87b001 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6d8759c..97491ec 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -110,6 +110,61 @@ typedef struct PartitionInfoData
 	PartitionRangeInfo	   *range;		/* range partition info */
 } PartitionInfoData;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo					PartitionKey executor state
+ *
+ *	pdesc					Info about immediate partitions (see
+ *							PartitionDescData)
+ *
+ *	index					If a partition ourselves, index in the parent's
+ *							partition array
+ *
+ *	num_leaf_partitions		Number of leaf partitions in the partition
+ *							tree rooted at this node
+ *
+ *	offset					0-based index of the first leaf partition
+ *							in the partition tree rooted at this node
+ *
+ *	downlink				Link to our leftmost child node (ie, corresponding
+ *							to first of our partitions that is itself
+ *							partitioned)
+ *
+ *	next					Link to the right sibling node on a given level
+ *							(ie, corresponding to the next partition on the same
+ *							level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
@@ -149,6 +204,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionCheckQual() */
 static List *generate_partition_check_qual(Relation rel);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -892,6 +951,53 @@ RelationGetPartitionCheckQual(Relation rel)
 	return generate_partition_check_qual(rel);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1491,6 +1597,106 @@ generate_partition_check_qual(Relation rel)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->parts[i]->oid;
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b2782f8..85384cb 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -30,6 +30,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -57,4 +58,8 @@ extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_check_qual_from_partbound(Relation rel, Relation parent,
 										   Node *bound);
 extern List *RelationGetPartitionCheckQual(Relation rel);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-3.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-3.patchDownload
From ab847366e344c3f713807758a25ffcb194ea6a09 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  348 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   46 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 +++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 907 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 97491ec..38cbc6f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -208,6 +208,18 @@ static List *generate_partition_check_qual(Relation rel);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionListInfo *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -236,6 +248,10 @@ static int32 partition_range_tuple_cmp(PartitionKey key,
 						   Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key,
 							PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple);
 
 /*
  * Partition key related functions
@@ -1613,7 +1629,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1697,6 +1713,270 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = copy_partition_key(rel->rd_partkey);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->parts[index]->oid, NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	int			i;
+
+	Assert(pdesc->nparts > 0);
+
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		int		j;
+
+		for (j = 0; j < pdesc->parts[i]->list->nvalues; j++)
+		{
+			if (isnull)
+			{
+				if (pdesc->parts[i]->list->nulls[j])
+					return i;
+				continue;
+			}
+
+			if (!pdesc->parts[i]->list->nulls[j] &&
+				partition_list_values_equal(key,
+											pdesc->parts[i]->list->values[j],
+											value))
+				return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+
+	return range_partition_bsearch(key, pdesc, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -2015,3 +2295,69 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * range_partition_bsearch
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+						Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = pdesc->nparts - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (pdesc->parts[idx]->range->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, pdesc->parts[idx]->range->upper))
+		{
+			if (pdesc->parts[idx]->range->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ba5bf23..6fe9b0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1362,6 +1368,94 @@ BeginCopy(bool is_from,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1657,6 +1751,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1670,6 +1766,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2212,6 +2325,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2232,7 +2346,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2340,6 +2455,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2367,6 +2483,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2388,10 +2505,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2443,6 +2596,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2463,7 +2641,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2493,7 +2680,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2513,6 +2701,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2526,7 +2720,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3cc45ef..e5ee8f7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3d82658..2f22b3f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionCheckQual(resultRelationDesc);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionCheckQual(resultRelationDesc);
 }
 
 /*
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..32f4031 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6152,6 +6153,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 70e7d4f..6ef45d5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1708,3 +1708,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..9f87f57 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -797,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 85384cb..14fd29e 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/relcache.h"
 
@@ -62,4 +64,9 @@ extern List *RelationGetPartitionCheckQual(Relation rel);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e35da66..39ca517 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-3.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-3.patchDownload
From 523358f44e4166c30ad05e6f0bf8ed1642ea4300 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a393813..253eeb4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#31Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#30)
Re: Declarative partitioning - another take

Hi,

I have applied updated patches given by you, and observe below.

here in the given example, t6_p3 partition is not allowed to have null, but
I am able to insert it, causing two nulls in the table.

--create a partition table
create table t6 (a int, b varchar) partition by list(a);
create table t6_p1 partition of t6 for values in (1,2,null);
create table t6_p2 partition of t6 for values in (4,5);
create table t6_p3 partition of t6 for values in (3,6);

--insert some values
insert into t6 select i,i::varchar from generate_series(1,6) i;
insert into t6 values (null,'A');

--try inserting null to t6_p3 partition table
insert into t6_p3 values (null,'A');

select tableoid::regclass,* from t6;
tableoid | a | b
----------+---+---
t6_p1 | 1 | 1
t6_p1 | 2 | 2
t6_p1 | | A
t6_p2 | 4 | 4
t6_p2 | 5 | 5
t6_p3 | 3 | 3
t6_p3 | 6 | 6
t6_p3 | | A
(8 rows)

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

On Tue, Sep 6, 2016 at 1:37 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Show quoted text

Hi,

On 2016/09/06 16:13, Ashutosh Bapat wrote:

I found a server crash while running make check in regress folder. with
this set of patches. Problem is RelationBuildPartitionKey() partexprsrc

may

be used uninitialized. Initializing it with NIL fixes the crash. Here's
patch to fix it. Came up with the fix after discussion with Amit.

Thanks for the report. Here is a rebased version of the patches including
you fix (no significant changes from those posted on Aug 26).

Thanks,
Amit

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

#32Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#20)
Re: Declarative partitioning - another take

On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.

Seems worth trying. One point that bothers me a bit is how do we enforce
partition bound condition on individual partition basis. For example when
a row is inserted into a partition directly, we better check that it does
not fall outside the bounds and issue an error otherwise. With current
approach, we just look up a partition's bound from the catalog and gin up
a check constraint expression (and cache in relcache) to be enforced in
ExecConstraints(). With the new approach, I guess we would need to look
up the parent's partition descriptor. Note that the checking in
ExecConstraints() is turned off when routing a tuple from the parent.

[ Sorry for the slow response. ]

Yeah, that's a problem. Maybe it's best to associate this data with
the childrels after all - or halfway in between, e.g. augment
pg_inherits with this information. After all, the performance problem
I was worried about above isn't really much of an issue: each backend
will build a relcache entry for the parent just once and then use it
for the lifetime of the session unless some invalidation occurs. So
if that takes a small amount of extra time, it's probably not really a
big deal. On the other hand, if we can't build the implicit
constraint for the child table without opening the parent, that's
probably going to cause us some serious inconvenience.

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

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

#33Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#16)
Re: Declarative partitioning - another take

This patch uses RangeBound structure. There's also a structure defined with
the same name in rangetypes.h with some slight differences. Should we
rename the one in partition.c as PartRangeBound or something like that to
avoid the confusion?

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

#34Amit Langote
amitlangote09@gmail.com
In reply to: Rajkumar Raghuwanshi (#31)
Re: Declarative partitioning - another take

Hi,

On Tue, Sep 6, 2016 at 8:15 PM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:

Hi,

I have applied updated patches given by you, and observe below.

here in the given example, t6_p3 partition is not allowed to have null, but
I am able to insert it, causing two nulls in the table.

--create a partition table
create table t6 (a int, b varchar) partition by list(a);
create table t6_p1 partition of t6 for values in (1,2,null);
create table t6_p2 partition of t6 for values in (4,5);
create table t6_p3 partition of t6 for values in (3,6);

--insert some values
insert into t6 select i,i::varchar from generate_series(1,6) i;
insert into t6 values (null,'A');

--try inserting null to t6_p3 partition table
insert into t6_p3 values (null,'A');

select tableoid::regclass,* from t6;
tableoid | a | b
----------+---+---
t6_p1 | 1 | 1
t6_p1 | 2 | 2
t6_p1 | | A
t6_p2 | 4 | 4
t6_p2 | 5 | 5
t6_p3 | 3 | 3
t6_p3 | 6 | 6
t6_p3 | | A
(8 rows)

Thanks for testing!

That looks like a bug. The same won't occur if you had inserted
through the parent (it would be correctly routed to the partition that
has been defined to accept nulls (that is, t6_p1 of your example). The
bug seems to have to do with the handling of nulls in generating
implicit constraints from the list of values of a given list
partition. The direct insert on t6_p1 should have failed because
partition key has a value (null) violating the partition boundary
condition. Will fix.

Thanks,
Amit

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

#35Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#32)
Re: Declarative partitioning - another take

On Tue, Sep 6, 2016 at 9:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.

Seems worth trying. One point that bothers me a bit is how do we enforce
partition bound condition on individual partition basis. For example when
a row is inserted into a partition directly, we better check that it does
not fall outside the bounds and issue an error otherwise. With current
approach, we just look up a partition's bound from the catalog and gin up
a check constraint expression (and cache in relcache) to be enforced in
ExecConstraints(). With the new approach, I guess we would need to look
up the parent's partition descriptor. Note that the checking in
ExecConstraints() is turned off when routing a tuple from the parent.

[ Sorry for the slow response. ]

Yeah, that's a problem. Maybe it's best to associate this data with
the childrels after all - or halfway in between, e.g. augment
pg_inherits with this information. After all, the performance problem
I was worried about above isn't really much of an issue: each backend
will build a relcache entry for the parent just once and then use it
for the lifetime of the session unless some invalidation occurs. So
if that takes a small amount of extra time, it's probably not really a
big deal. On the other hand, if we can't build the implicit
constraint for the child table without opening the parent, that's
probably going to cause us some serious inconvenience.

Agreed. So I will stick with the existing approach.

Thanks,
Amit

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

#36Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#35)
Re: Declarative partitioning - another take

Hi,

I have a query regarding list partitioning,

For example if I want to store employee data in a table, with "IT" dept
employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and if
employee belongs to other than these two, should come in emp_p3 partition.

In this case not sure how to create partition table. Do we have something
like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
list partition.

create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (??);

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

On Tue, Sep 6, 2016 at 6:37 PM, Amit Langote <amitlangote09@gmail.com>
wrote:

Show quoted text

On Tue, Sep 6, 2016 at 9:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.

Seems worth trying. One point that bothers me a bit is how do we

enforce

partition bound condition on individual partition basis. For example

when

a row is inserted into a partition directly, we better check that it

does

not fall outside the bounds and issue an error otherwise. With current
approach, we just look up a partition's bound from the catalog and gin

up

a check constraint expression (and cache in relcache) to be enforced in
ExecConstraints(). With the new approach, I guess we would need to look
up the parent's partition descriptor. Note that the checking in
ExecConstraints() is turned off when routing a tuple from the parent.

[ Sorry for the slow response. ]

Yeah, that's a problem. Maybe it's best to associate this data with
the childrels after all - or halfway in between, e.g. augment
pg_inherits with this information. After all, the performance problem
I was worried about above isn't really much of an issue: each backend
will build a relcache entry for the parent just once and then use it
for the lifetime of the session unless some invalidation occurs. So
if that takes a small amount of extra time, it's probably not really a
big deal. On the other hand, if we can't build the implicit
constraint for the child table without opening the parent, that's
probably going to cause us some serious inconvenience.

Agreed. So I will stick with the existing approach.

Thanks,
Amit

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

#37Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#36)
Re: Declarative partitioning - another take

Hi,

On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:

Hi,

I have a query regarding list partitioning,

For example if I want to store employee data in a table, with "IT" dept
employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and if
employee belongs to other than these two, should come in emp_p3 partition.

In this case not sure how to create partition table. Do we have something
like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
list partition.

create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (??);

Sorry, no such feature is currently offered. It might be possible to
offer something like a "default" list partition which accepts values other
than those specified for other existing partitions. However, that means
if we add a non-default list partition after a default one has been
created, the implementation must make sure that it moves any values from
the default partition that now belong to the newly created partition.

Thanks,
Amit

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

#38Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#37)
Re: Declarative partitioning - another take

On Wed, Sep 7, 2016 at 3:58 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Hi,

On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:

Hi,

I have a query regarding list partitioning,

For example if I want to store employee data in a table, with "IT" dept
employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and

if

employee belongs to other than these two, should come in emp_p3

partition.

In this case not sure how to create partition table. Do we have something
like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
list partition.

create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (??);

Sorry, no such feature is currently offered. It might be possible to
offer something like a "default" list partition which accepts values other
than those specified for other existing partitions. However, that means
if we add a non-default list partition after a default one has been
created, the implementation must make sure that it moves any values from
the default partition that now belong to the newly created partition.

Thanks,
Amit

Thanks for clarifying, But I could see same problem of moving data when
adding a new non-default partition with unbounded range partition.

For example give here, Initially I have create a partition table test with
test_p3 as unbounded end,
Later tried to change test_p3 to contain 7-9 values only, and adding a new
partition test_p4 contain 10-unbound.

--create partition table and some leafs
CREATE TABLE test (a int, b int) PARTITION BY RANGE(a);
CREATE TABLE test_p1 PARTITION OF test FOR VALUES START (1) END (4);
CREATE TABLE test_p2 PARTITION OF test FOR VALUES START (4) END (7);
CREATE TABLE test_p3 PARTITION OF test FOR VALUES START (7) END UNBOUNDED;

--insert some data
INSERT INTO test SELECT i, i*10 FROM generate_series(1,3) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(4,6) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(7,13) i;

--directly not able to attach test_p4 because of overlap error, hence
detached test_p3 and than attaching test_p4
SELECT tableoid::regclass,* FROM test;
tableoid | a | b
----------+----+-----
test_p1 | 1 | 10
test_p1 | 2 | 20
test_p1 | 3 | 30
test_p2 | 4 | 40
test_p2 | 5 | 50
test_p2 | 6 | 60
test_p3 | 7 | 70
test_p3 | 8 | 80
test_p3 | 9 | 90
test_p3 | 10 | 100
test_p3 | 11 | 110
test_p3 | 12 | 120
test_p3 | 13 | 130
(13 rows)

ALTER TABLE test DETACH PARTITION test_p3;
CREATE TABLE test_p4 (like test);
ALTER TABLE test ATTACH PARTITION test_p4 FOR VALUES start (10) end
UNBOUNDED;

--now can not attach test_p3 because of overlap with test_p4, causing data
loss from main test table.
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (10);
ERROR: source table contains a row violating partition bound specification
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (13);
ERROR: partition "test_p3" would overlap partition "test_p4"

SELECT tableoid::regclass,* FROM test;
tableoid | a | b
----------+---+----
test_p1 | 1 | 10
test_p1 | 2 | 20
test_p1 | 3 | 30
test_p2 | 4 | 40
test_p2 | 5 | 50
test_p2 | 6 | 60
(6 rows)

#39Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#38)
Re: Declarative partitioning - another take

On 2016/09/08 21:38, Rajkumar Raghuwanshi wrote:

On Wed, Sep 7, 2016 at 3:58 PM, Amit Langote wrote:

On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:

In this case not sure how to create partition table. Do we have something
like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
list partition.

create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (??);

Sorry, no such feature is currently offered. It might be possible to
offer something like a "default" list partition which accepts values other
than those specified for other existing partitions. However, that means
if we add a non-default list partition after a default one has been
created, the implementation must make sure that it moves any values from
the default partition that now belong to the newly created partition.

Thanks for clarifying, But I could see same problem of moving data when
adding a new non-default partition with unbounded range partition.

For example give here, Initially I have create a partition table test with
test_p3 as unbounded end,
Later tried to change test_p3 to contain 7-9 values only, and adding a new
partition test_p4 contain 10-unbound.

--create partition table and some leafs
CREATE TABLE test (a int, b int) PARTITION BY RANGE(a);
CREATE TABLE test_p1 PARTITION OF test FOR VALUES START (1) END (4);
CREATE TABLE test_p2 PARTITION OF test FOR VALUES START (4) END (7);
CREATE TABLE test_p3 PARTITION OF test FOR VALUES START (7) END UNBOUNDED;

--insert some data
INSERT INTO test SELECT i, i*10 FROM generate_series(1,3) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(4,6) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(7,13) i;

--directly not able to attach test_p4 because of overlap error, hence
detached test_p3 and than attaching test_p4
SELECT tableoid::regclass,* FROM test;
tableoid | a | b
----------+----+-----
test_p1 | 1 | 10
test_p1 | 2 | 20
test_p1 | 3 | 30
test_p2 | 4 | 40
test_p2 | 5 | 50
test_p2 | 6 | 60
test_p3 | 7 | 70
test_p3 | 8 | 80
test_p3 | 9 | 90
test_p3 | 10 | 100
test_p3 | 11 | 110
test_p3 | 12 | 120
test_p3 | 13 | 130
(13 rows)

ALTER TABLE test DETACH PARTITION test_p3;
CREATE TABLE test_p4 (like test);
ALTER TABLE test ATTACH PARTITION test_p4 FOR VALUES start (10) end
UNBOUNDED;

--now can not attach test_p3 because of overlap with test_p4, causing data
loss from main test table.
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (10);
ERROR: source table contains a row violating partition bound specification
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (13);
ERROR: partition "test_p3" would overlap partition "test_p4"

In this particular case, you will have to move any rows in test_p3 with
key > or >= 10 into the new partition test_p4 using dml (to not lose any
data). Then attach test_p3 as partition for values start (7) end (10);
you won't get either of the above errors.

Looking forward, what we need I think is a split partition command.
Adding a new partition that overlaps the default list partition or
unbounded range partition could be done by splitting the latter. Perhaps
something like:

alter table test
split partition test_p3 at (10) [inclusive | exclusive] with test_p4;

The above command would make test_p3 into 2 partitions:

test_p3 start (7) end (10) [inclusive | exclusive]
test_p4 start (10) [exclusive | inclusive] end unbounded

Any rows in test_p3 with key > or >= 10 will be moved into the newly
created test_p4 as part of the execution of this command.

For your list partitioning example:

create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (default);

alter table emp
split partition emp_p3 with emp_p3 ('ACCT') emp_p4 (default);

Any rows in emp_p3 with key != 'ACCT' will be moved into the newly created
default partition emp_p4.

But for time being, I think we could provide the syntax and mechanism for
default list partition seeing as we have the same for range partitioned
table (namely a range partition with unbounded start or end). Although
with the limitations as discussed.

Thoughts?

Thanks,
Amit

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

#40Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#34)
9 attachment(s)
Re: Declarative partitioning - another take

On 2016/09/06 22:04, Amit Langote wrote:

Will fix.

Here is an updated set of patches.

In addition to fixing a couple of bugs reported by Ashutosh and Rajkumar,
there are a few of major changes:

* change the way individual partition bounds are represented internally
and the way a collection of partition bounds associated with a
partitioned table is exposed to other modules. Especially list
partition bounds which are manipulated more efficiently as discussed
at [1]/messages/by-id/CA+TgmoZCr0-t93KgJA3T1uy9yWxfYaSYL3X35ObyHg+ZUfERqQ@mail.gmail.com.

* \d partitioned_table now shows partition count and \d+ lists partition
names and their bounds as follows:

\d t6
Table "public.t6"
Column | Type | Modifiers
.-------+-------------------+-----------
a | integer |
b | character varying |
Partition Key: LIST (a)
Number of partitions: 3 (Use \d+ to list them.)

\d+ t6
Table "public.t6"
Column | Type | Modifiers | Storage | Stats target |
Description
.-------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | character varying | | extended | |
Partition Key: LIST (a)
Partitions: t6_p1 FOR VALUES IN (1, 2, NULL),
t6_p2 FOR VALUES IN (4, 5),
t6_p3 FOR VALUES IN (3, 6)

\d+ p
Table "public.p"
Column | Type | Modifiers | Storage | Stats target | Description
.-------+--------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | character(1) | | extended | |
Partition Key: RANGE (a)
Partitions: p1 FOR VALUES START (1) END (10),
p2 FOR VALUES START (10) END (20),
p3 FOR VALUES START (20) END (30),
p4 FOR VALUES START (30) EXCLUSIVE END (40) INCLUSIVE,
p5 FOR VALUES START (40) EXCLUSIVE END (50),
p6 FOR VALUES START (50) END UNBOUNDED

* Some more regression tests

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZCr0-t93KgJA3T1uy9yWxfYaSYL3X35ObyHg+ZUfERqQ@mail.gmail.com
/messages/by-id/CA+TgmoZCr0-t93KgJA3T1uy9yWxfYaSYL3X35ObyHg+ZUfERqQ@mail.gmail.com

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-4.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-4.patchDownload
From 29c9d232c6ab3d45e14522fd9f9e11fdad06eb4d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                    |  102 +++++++-
 doc/src/sgml/ref/create_table.sgml            |   55 ++++
 src/backend/access/common/reloptions.c        |    2 +
 src/backend/catalog/Makefile                  |    6 +-
 src/backend/catalog/aclchk.c                  |    2 +
 src/backend/catalog/dependency.c              |    2 +
 src/backend/catalog/heap.c                    |   27 ++-
 src/backend/catalog/objectaddress.c           |    5 +-
 src/backend/catalog/partition.c               |  394 +++++++++++++++++++++++++
 src/backend/catalog/pg_depend.c               |    3 +
 src/backend/catalog/pg_partitioned_table.c    |  172 +++++++++++
 src/backend/commands/analyze.c                |    2 +
 src/backend/commands/copy.c                   |    6 +
 src/backend/commands/indexcmds.c              |    7 +-
 src/backend/commands/lockcmds.c               |    2 +-
 src/backend/commands/policy.c                 |    2 +-
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/sequence.c               |    1 +
 src/backend/commands/tablecmds.c              |  384 ++++++++++++++++++++++++-
 src/backend/commands/trigger.c                |    7 +-
 src/backend/commands/vacuum.c                 |    1 +
 src/backend/executor/execMain.c               |    2 +
 src/backend/executor/nodeModifyTable.c        |    1 +
 src/backend/nodes/copyfuncs.c                 |   33 ++
 src/backend/nodes/equalfuncs.c                |   28 ++
 src/backend/nodes/outfuncs.c                  |   26 ++
 src/backend/parser/gram.y                     |  110 ++++++--
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_expr.c               |    5 +
 src/backend/parser/parse_utilcmd.c            |   69 +++++
 src/backend/rewrite/rewriteDefine.c           |    1 +
 src/backend/rewrite/rewriteHandler.c          |    1 +
 src/backend/tcop/utility.c                    |    5 +-
 src/backend/utils/cache/relcache.c            |   19 ++-
 src/backend/utils/cache/syscache.c            |   12 +
 src/include/catalog/dependency.h              |    8 +-
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/partition.h               |   35 +++
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_partitioned_table.h    |   69 +++++
 src/include/catalog/pg_partitioned_table_fn.h |   29 ++
 src/include/commands/defrem.h                 |    2 +
 src/include/nodes/nodes.h                     |    2 +
 src/include/nodes/parsenodes.h                |   35 +++
 src/include/parser/kwlist.h                   |    1 +
 src/include/parser/parse_node.h               |    3 +-
 src/include/pg_config_manual.h                |    5 +
 src/include/utils/rel.h                       |    9 +
 src/include/utils/syscache.h                  |    1 +
 src/test/regress/expected/alter_table.out     |   46 +++
 src/test/regress/expected/create_table.out    |  155 ++++++++++
 src/test/regress/expected/sanity_check.out    |    1 +
 src/test/regress/sql/alter_table.sql          |   34 +++
 src/test/regress/sql/create_table.sql         |  133 +++++++++
 54 files changed, 2033 insertions(+), 45 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/backend/catalog/pg_partitioned_table.c
 create mode 100644 src/include/catalog/partition.h
 create mode 100644 src/include/catalog/pg_partitioned_table.h
 create mode 100644 src/include/catalog/pg_partitioned_table_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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>partedrelid</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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partkey</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns and/or expressions in the partition key and partition
+      rules associated with the partitions.
+     </para>
+
+     <para>
+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_type.o storage.o toasting.o pg_partitioned_table.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_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
 					 getObjectDescription(object));
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				subflags = 0;	/* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
+	/* Remove NO INHERIT flag if rel is a partitioned table */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
 	/*
 	 * Create the Check Constraint
 	 */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.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_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+	Oid		*typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+	char	  **partcolnames;	/* partition key column names */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+								KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	int2vector	   *partattrs;
+	oidvector	   *opclass;
+	KeyTypeCollInfo *tcinfo;
+	ListCell	   *partexprbin_item;
+	List		   *partexprsrc = NIL;
+	ListCell	   *partexprsrc_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	/* Allocate in the supposedly short-lived working context */
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/* Open the catalog for its tuple descriptor */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	partattrs = (int2vector *) DatumGetPointer(datum);
+
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprbin,
+						 RelationGetDescr(catalog),
+						 &isnull);
+
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+
+		/* We should have a partexprsrc as well */
+		datum = heap_getattr(tuple,
+							 Anum_pg_partitioned_table_partexprsrc,
+							 RelationGetDescr(catalog),
+							 &isnull);
+		Assert(!isnull);
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+		partexprsrc = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+	tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+	tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather partition column names (simple C strings) */
+	key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+	/* Copy partattrs and fill other per-attribute info */
+	partexprbin_item = list_head(key->partexprs);
+	partexprsrc_item = list_head(partexprsrc);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		HeapTuple		tuple;
+		AttrNumber		attno;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		key->partattrs[i] = attno = partattrs->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+			tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+			tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+		}
+		get_typlenbyvalalign(tcinfo->typid[i],
+							 &tcinfo->typlen[i],
+							 &tcinfo->typbyval[i],
+							 &tcinfo->typalign[i]);
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+
+		/* Collect atttribute names */
+		if (key->partattrs[i] != 0)
+			key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+															key->partattrs[i]);
+		else
+		{
+			Value *str = lfirst(partexprsrc_item);
+			key->partcolnames[i] = pstrdup(str->val.str);
+			partexprsrc_item = lnext(partexprsrc_item);
+		}
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+	return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				i;
+
+	newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *)
+							palloc0(newkey->partnatts * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs,
+							newkey->partnatts * sizeof(AttrNumber));
+
+	newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *)
+							palloc0(newkey->partnatts * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+	newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+											 fromkey->tcinfo);
+	newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+	for (i = 0; i < newkey->partnatts; i++)
+		newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+	return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+	KeyTypeCollInfo   *result = (KeyTypeCollInfo *)
+								palloc0(sizeof(KeyTypeCollInfo));
+
+	result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+	result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+	memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+	result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+	memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+	result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+	result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+	result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+	return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
 
+	if (behavior == DEPENDENCY_IGNORE)
+		return;					/* nothing to do */
+
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
 
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..fa4d0f5
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ *	  routines to support manipulation of the pg_partitioned_table 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_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprbin,
+				  List *partexprsrc,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprbinDatum;
+	Datum		partexprsrcDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/*
+	 * Copy the partition key, opclass info into arrays (should we
+	 * make the caller pass them like this to start with?)
+	 */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprbin)
+	{
+		char       *exprbinString;
+		char       *exprsrcString;
+
+		exprbinString = nodeToString(partexprbin);
+		exprsrcString = nodeToString(partexprsrc);
+		partexprbinDatum = CStringGetTextDatum(exprbinString);
+		partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+		pfree(exprbinString);
+		pfree(exprsrcString);
+	}
+	else
+		partexprbinDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprbinDatum)
+	{
+		nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+		nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+	}
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+	values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprbin)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprbin,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_IGNORE);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
 	bool		concurrent;
 };
 
+/* for find_attr_reference_walker */
+typedef struct
+{
+	AttrNumber	attnum;
+} find_attr_reference_context;
+
 /* Alter table target-type flags for ATSimplePermissions */
 #define		ATT_TABLE				0x0001
 #define		ATT_VIEW				0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass);
 
 
 /* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partby)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprbin = NIL;
+		List		   *partexprsrc = NIL;
+
+		stmt->partby = transformPartitionBy(rel, stmt->partby);
+		ComputePartitionAttrs(rel, stmt->partby->partParams,
+							  partattrs, &partexprbin, &partexprsrc,
+							  partopclass);
+
+		partnatts = list_length(stmt->partby->partParams);
+		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+						  partattrs, partexprbin, partexprsrc, partopclass);
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
+	 * RELKIND_RELATION while the relation is actually a partitioned table.
+	 */
+	if (classform->relkind != relkind &&
+				(relkind == RELKIND_RELATION &&
+					classform->relkind != RELKIND_PARTITIONED_TABLE))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var		   *variable = (Var *) node;
+		AttrNumber	attnum = variable->varattno;
+
+		if (attnum == context->attnum)
+			return true;
+	}
+
+	return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber partatt = get_partition_col_attnum(key, i);
+
+		if(partatt != 0)
+		{
+			*is_expr = false;
+			if (attnum == partatt)
+				return true;
+		}
+		else
+		{
+			find_attr_reference_context context;
+
+			*is_expr = true;
+			context.attnum = attnum;
+			if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+				return true;
+
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+	PartitionBy	   *partby;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	partby = (PartitionBy *) makeNode(PartitionBy);
+
+	partby->strategy = partitionby->strategy;
+	partby->location = partitionby->location;
+	partby->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+
+			/*
+			 * transformExpr() should have already rejected subqueries,
+			 * aggregates, and window functions, based on the EXPR_KIND_ for
+			 * a partition key expression.
+			 *
+			 * Also reject expressions returning sets; this is for consistency
+			 * DefineRelation() will make more checks.
+			 */
+			if (expression_returns_set(pelem->expr))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("partition key expression cannot return a set"),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		partby->partParams = lappend(partby->partParams, pelem);
+	}
+
+	return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			if (IsA(expr, CollateExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot use COLLATE in partition key expression")));
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				char   *exprsrc;
+
+				partattrs[attn] = 0; /* marks expression */
+				*partexprbin = lappend(*partexprbin, expr);
+
+				/*
+				 * transformExpr() should have already rejected subqueries,
+				 * aggregates, and window functions, based on the EXPR_KIND_
+				 * for a partition key expression.
+				 */
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use a constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				exprsrc = deparse_expression(expr,
+							deparse_context_for(RelationGetRelationName(rel),
+												RelationGetRelid(rel)),
+									   false, false);
+				*partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+			}
+		}
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify an existing btree operator class or define one for the type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4f39dad..37a60b6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3017,6 +3017,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4173,6 +4174,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+	PartitionBy *newnode = makeNode(PartitionBy);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5087,6 +5114,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionBy:
+			retval = _copyPartitionBy(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4800165..a321ac1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2633,6 +2634,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3386,6 +3408,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionBy:
+			retval = _equalPartitionBy(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 90fecb1..ab75085 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3280,6 +3281,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3864,6 +3885,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionBy:
+				_outPartitionBy(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a95a65a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partby>		PartitionBy OptPartitionBy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partby = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partby = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partby = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partby = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * 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 7a2950e..85d67c1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partby != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partby)
+	{
+		int		partnatts = list_length(stmt->partby->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2518,6 +2586,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..f19479d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -983,10 +983,13 @@ ProcessUtilitySlow(ParseState *pstate,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partby != NULL
+													? RELKIND_PARTITIONED_TABLE
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
  * created only during initdb.  The fields for the dependent object
  * contain zeroes.
  *
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question.  However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
  * Other dependency flavors may be needed in future.
  */
 
@@ -77,7 +82,8 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
+	DEPENDENCY_IGNORE = 'g'
 } DependencyType;
 
 /*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* 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
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+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);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..db54358
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprbin;	/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+	pg_node_tree	partexprsrc;
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partexprbin	6
+#define Anum_pg_partitioned_table_partexprsrc	7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprbin,
+					List *partexprsrc,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif   /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionBy,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8d3dcf4..afaa4d3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,6 +698,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionBy
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionBy;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1752,6 +1786,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR:  cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-4.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-4.patchDownload
From f8a83d23be2aca0ac374c0054ecbf7d1636e51af Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 019f75a..5e5c370 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-4.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-4.patchDownload
From 9dcfedb6ad2693c2ec85578b839190cac35d09d6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 contrib/pg_trgm/trgm_op.c                  |    2 +-
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/heap.c                 |   35 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1689 +++++++++++++++++++++++++++-
 src/backend/catalog/pg_partitioned_table.c |    2 -
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/lockcmds.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  774 +++++++++++--
 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         |  331 ++++++-
 src/backend/utils/cache/relcache.c         |  104 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   39 +
 src/include/catalog/pg_class.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/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  188 +++
 src/test/regress/sql/alter_table.sql       |  192 ++++
 src/test/regress/sql/create_table.sql      |  138 +++
 36 files changed, 4291 insertions(+), 166 deletions(-)

diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index dd0f492..1cfd341 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -1023,7 +1023,7 @@ trgm_presence_map(TRGM *query, TRGM *key)
 
 	result = (bool *) palloc0(lenq * sizeof(bool));
 
-	/* for each query trigram, do a binary search in the key array */
+/* for each query trigram, do a binary search in the key array */
 	for (i = 0; i < lenq; i++)
 	{
 		int			lo = 0;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1229,6 +1311,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..007782c 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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..16e4d22 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2054,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
-	/* Remove NO INHERIT flag if rel is a partitioned table */
+	/* Remove NO INHERIT flag if rel is a partitioned table or a partition */
 	if (is_no_inherit &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2475,7 +2494,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..530bc97 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * partition.c
- *        Partitioning related utility functions.
+ *        Partitioning related data structures and functions.
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
 #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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/ruleutils.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /* Type and collation information for partition key columns */
@@ -69,11 +77,151 @@ typedef struct PartitionKeyData
 	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
 } PartitionKeyData;
 
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
 								KeyTypeCollInfo *tcinfo);
 
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_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,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionRangeSpec *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionRangeSpec *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionListSpec *list);
+static List *get_qual_for_range(PartitionKey key, PartitionRangeSpec *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionListSpec *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
 /*
  * Partition key related functions
  */
@@ -392,3 +540,1542 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
 
 	return result;
 }
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionListSpec  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionListSpec));
+					list_spec = (PartitionListSpec *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->tcinfo->typbyval[0],
+													  key->tcinfo->typlen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionRangeSpec  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionRangeSpec));
+					range_spec = (PartitionRangeSpec *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				ListInfo *listinfo;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+
+				/*
+				 * Copy oids in the same order as they were found in the
+				 * pg_inherits catalog
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[i] = oids[i];
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				if (found_null_partition)
+					listinfo->null_index = null_partition_index;
+				else
+					listinfo->null_index = -1;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->tcinfo->typbyval[0],
+													key->tcinfo->typlen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+				}
+
+				result->bounds->listinfo = listinfo;
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			PartitionListSpec *list;
+
+			Assert(IsA(bound, PartitionListSpec));
+			list = (PartitionListSpec *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, 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("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionListSpec))
+	{
+		PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionRangeSpec))
+	{
+		PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionListSpec *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionRangeSpec *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionListSpec (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionListSpec *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1, key->partattrs[0],
+									key->tcinfo->typid[0],
+									key->tcinfo->typmod[0],
+									key->tcinfo->typcoll[0],
+									0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr. 
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+	arr->array_collid = key->tcinfo->typcoll[0];
+	arr->element_typeid = key->tcinfo->typid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel)
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0], -1,
+							   get_typcollation(key->partopcintype[0]),
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->tcinfo->typcoll[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionRangeSpec (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_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 *nulltest;
+
+		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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  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	   *nulltest;
+
+		/* 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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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
+			 */
+			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 ge/gt lower_val */
+			strategy = 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 < 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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionListSpec
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										key->tcinfo->typbyval[0],
+										key->tcinfo->typlen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+	int	   *mapping;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+	/*
+	 * Index start from one.  A zero in nth slot means that partition n of
+	 * the first collection has not yet been mapped with a partition from
+	 * the second collection.
+	 */
+	mapping = (int *) palloc0((n + 1) * sizeof(int));
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		int		l1_partno = l1->indexes[i] + 1,
+				l2_partno = l2->indexes[i] + 1;
+
+		/* Encountered l1_partno for the first time, map to l2_partno */
+		if (mapping[l1_partno] == 0)
+			mapping[l1_partno] = l2_partno;
+		/*
+		 * Maintain that the mapping does not change, otherwise report
+		 * inequality
+		 */
+		else if (mapping[l1_partno] != l2_partno)
+			return false;
+	}
+
+	/* Check that nulls are accepted in mapped partitions */
+	Assert(l1->has_null || l1->null_index == -1);
+	Assert(l2->has_null || l2->null_index == -1);
+	if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+									  key->tcinfo->typcoll[0],
+									  val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionRangeSpec
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index fa4d0f5..453feb2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
 										RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
 										DEPENDENCY_IGNORE);
-	/* Tell world about the key */
-	CacheInvalidateRelcache(rel);
 
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 	heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 3bb5933..4a7c98d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,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 */
+	List	   *partition_quals; /* partition check quals 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 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprbin, List **partexprsrc,
 					  Oid *partopclass);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
 	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1452,7 +1494,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1497,8 +1540,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 			/*
 			 * Typed table column option that does not belong to a column from
-			 * the type.  This works because the columns from the type come
-			 * first in the list.
+			 * the type or the partition parent.  This works because the columns
+			 * from the type or the partition parent come first in the list.
 			 */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
 	{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2581,11 +2669,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2827,8 +2916,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3494,7 +3597,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3743,6 +3852,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);
@@ -3928,7 +4043,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4008,6 +4123,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4072,6 +4188,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4386,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4589,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4780,6 +4912,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5302,6 +5439,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5989,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5924,9 +6086,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6479,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7877,7 +8043,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);
@@ -7936,8 +8104,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8179,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10176,6 +10351,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10368,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;
 
@@ -10238,37 +10413,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10452,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);
 
@@ -10317,16 +10529,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;
 }
 
 /*
@@ -10377,7 +10581,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
@@ -10395,12 +10599,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10630,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10440,8 +10652,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10456,7 +10670,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10470,7 +10686,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.
@@ -10489,10 +10705,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10539,7 +10759,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10547,7 +10769,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10558,6 +10782,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10571,7 +10796,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10582,6 +10809,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10595,13 +10862,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];
@@ -10610,19 +10875,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10888,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);
 
@@ -10653,11 +10909,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
@@ -10666,7 +10931,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)))
@@ -10728,7 +10993,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);
 
@@ -10759,7 +11024,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)
@@ -10771,30 +11036,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;
 }
 
 /*
@@ -12431,3 +12686,272 @@ ComputePartitionAttrs(Relation rel, 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,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 37a60b6..3e800fe 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3017,6 +3018,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);
@@ -4200,6 +4202,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
  * ****************************************************************
@@ -5120,6 +5159,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 a321ac1..7dfe8c0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,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);
@@ -2375,6 +2376,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);
@@ -2655,6 +2657,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
  */
@@ -3414,6 +3447,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 ab75085..f9972c1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,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);
@@ -2587,6 +2588,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);
@@ -3302,6 +3304,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'
@@ -3891,6 +3912,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 894a48f..3dfdc37 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2265,6 +2265,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
@@ -2496,6 +2525,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 a95a65a..50657af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,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
@@ -601,7 +611,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11149,6 +11348,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;
@@ -13748,6 +13948,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13794,6 +13995,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13836,6 +14038,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 85d67c1..9d0438c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,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);
 
 
 /*
@@ -231,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +258,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")));
@@ -360,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);
@@ -899,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;
@@ -1118,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;
@@ -2587,6 +2602,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2675,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;
@@ -3039,3 +3071,300 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	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_partition_col_name(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_partition_col_name(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_partition_col_name(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 8cbd6e7..cd4f7f4 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);
 
 
 /*
@@ -925,6 +927,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1052,13 +1107,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2055,6 +2115,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2203,11 +2267,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2218,6 +2283,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);
@@ -2248,6 +2314,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
@@ -2303,6 +2372,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 */
@@ -3529,6 +3605,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5052,6 +5142,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..f515d4d 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,10 +14,38 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "parser/parse_node.h"
 #include "utils/relcache.h"
 
 typedef struct PartitionKeyData *PartitionKey;
 
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
 
@@ -32,4 +60,15 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
 extern int32 get_partition_col_typmod(PartitionKey key, int col);
 extern char *get_partition_col_name(PartitionKey key, int col);
 
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 #endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,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 afaa4d3..b925b89 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) */
@@ -733,6 +734,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
  ****************************************************************************/
@@ -1560,7 +1595,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
@@ -1785,7 +1822,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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
 Partition Key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- 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

0004-psql-and-pg_dump-support-for-partitions-4.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-4.patchDownload
From 385ab8bfceafe99f4bcd9b44735f9c524c09ef27 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++++----
 src/test/regress/expected/create_table.out |   39 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 ++++
 7 files changed, 393 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..4bc4e91 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c805a84..40ab7cc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6134,6 +6134,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15311,6 +15368,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15339,8 +15407,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15369,7 +15440,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15434,15 +15506,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15612,6 +15691,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 10d924a..0b763ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2b12276..732bf80 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,45 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index c38312e..8f52a85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-4.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-4.patchDownload
From e47f1315277691f6562a6229a81556ceaed461b7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
 src/backend/optimizer/prep/prepunion.c |  278 ++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 2 files changed, 204 insertions(+), 83 deletions(-)

diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..193b2c9 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/* Do not flatten the inheritance hierarchy if partitioned table */
+	if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (childOID != parentOID)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
-		 */
-		if (oldrc)
-		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
-		}
-
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-4.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-4.patchDownload
From b8aea0365d49ede203fce9b90266c32dc69d4abb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e28477d..e35da66 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-4.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-4.patchDownload
From b67976c0eb99b7fe044b0c7f0bfc6300d58af069 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 530bc97..fb1ab0e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -160,6 +160,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo					PartitionKey executor state
+ *
+ *	pdesc					Info about immediate partitions (see
+ *							PartitionDescData)
+ *
+ *	index					If a partition ourselves, index in the parent's
+ *							partition array
+ *
+ *	num_leaf_partitions		Number of leaf partitions in the partition
+ *							tree rooted at this node
+ *
+ *	offset					0-based index of the first leaf partition
+ *							in the partition tree rooted at this node
+ *
+ *	downlink				Link to our leftmost child node (ie, corresponding
+ *							to first of our partitions that is itself
+ *							partitioned)
+ *
+ *	next					Link to the right sibling node on a given level
+ *							(ie, corresponding to the next partition on the same
+ *							level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
@@ -201,6 +256,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -1067,6 +1126,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1711,6 +1817,106 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->parts[i]->oid;
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index f515d4d..d3e789a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -45,6 +45,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -71,4 +72,8 @@ extern Oid get_partition_parent(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-4.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-4.patchDownload
From 690ef5127fd8f7d81b954e1462b65ace542e8f60 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  347 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 +++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 911 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fb1ab0e..8f6cbc9 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -260,6 +260,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -280,6 +292,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * Partition key related functions
@@ -1167,7 +1182,7 @@ get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
 			node = node->next;
 		}
 		else
-			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
 	}
 
 	return result;
@@ -1833,7 +1848,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1851,7 +1866,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 	prev = NULL;
 	for (i = 0; i < parent->pdesc->nparts; i++)
 	{
-		Oid			relid = parent->pdesc->parts[i]->oid;
+		Oid			relid = parent->pdesc->oids[i];
 		int			offset;
 		Relation	rel;
 		PartitionTreeNode child;
@@ -1917,6 +1932,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = copy_partition_key(rel->rd_partkey);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -2285,3 +2561,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 157d219..932ed62 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1690,6 +1786,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2260,7 +2374,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2416,10 +2533,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2471,6 +2624,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2491,7 +2669,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2521,7 +2708,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2554,7 +2748,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4a7c98d..152c575 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..32f4031 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6152,6 +6153,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eac86cc..9f87f57 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -797,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index d3e789a..badd566 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/relcache.h"
 
@@ -76,4 +78,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e35da66..39ca517 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-4.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-4.patchDownload
From 724d3b127c5a13a36cbaab77adfb5535ff89a871 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a393813..253eeb4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#41Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#40)
9 attachment(s)
Re: Declarative partitioning - another take

Hi

On 2016/09/09 17:55, Amit Langote wrote:

On 2016/09/06 22:04, Amit Langote wrote:

Will fix.

Here is an updated set of patches.

An email from Rajkumar somehow managed to break out of this thread.
Quoting his message below so that I don't end up replying with patches on
two different threads.

On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:

I have Continued with testing declarative partitioning with the latest
patch. Got some more observation, given below

Thanks a lot for testing.

-- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
partition.

create table test_range_bound ( a int) partition by range(a);
--creating a partition to contain records {1,2,3,4}, by default 1 is
inclusive and 5 is exclusive
create table test_range_bound_p1 partition of test_range_bound for values
start (1) end (5);
--now trying to create a partition by explicitly mentioning start is
exclusive to contain records {5,6,7}, here trying to create with START with
4 as exclusive so range should be 5 to 8, but getting partition overlap
error.
create table test_range_bound_p2 partition of test_range_bound for values
start (4) EXCLUSIVE end (8);
ERROR: partition "test_range_bound_p2" would overlap partition
"test_range_bound_p1"

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command. Range types do this
and hence an equivalent test done with range type values would disagree
with the result given by the patch for range partition bounds.

select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
cmp
-----
f
(1 row)

In this case, the second range is converted into its equivalent canonical
form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
ranges do not overlap after all. Range type operators can do this because
their code can rely on the availability of a canonicalization function for
a given range type. From the range types documentation:

"""
A discrete range type should have a canonicalization function that is
aware of the desired step size for the element type. The canonicalization
function is charged with converting equivalent values of the range type to
have identical representations, in particular consistently inclusive or
exclusive bounds. If a canonicalization function is not specified, then
ranges with different formatting will always be treated as unequal, even
though they might represent the same set of values in reality.
"""

to extend the last sentence:

"... or consider two ranges overlapping when in reality they are not
(maybe they are really just adjacent)."

Within the code handling range partition bound, no such canonicalization
happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
hence ranges overlap.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

One more option is we let the user specify the canonicalize function next
to the column name when defining the partition key. If not specified, we
hard-code one for the types for which we will be implementing a
canonicalize function (ie, above mentioned types). In other cases, we
just don't have one and hence if an unexpected result occurs when creating
a new partition, it's up to the user to realize what happened. Of course,
we will be mentioning in the documentation why a canonicalize function is
necessary and how to write one. Note that this canonicalize function
comes into play only when defining new partitions, it has no role beyond
that point.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

\d+ test_subpart
Table "public.test_subpart"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Key: RANGE (c1)
Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE

\d+ test_subpart_p1
Table "public.test_subpart_p1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
Partition Key: RANGE (c1)
Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)

insert into test_subpart values (50);
ERROR: no partition of relation "test_subpart_p1" found for row
DETAIL: Failing row contains (50).
insert into test_subpart values (150);
ERROR: no partition of relation "test_subpart" found for row
DETAIL: Failing row contains (150).

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.

CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');

INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);

postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
tableoid | i | j | k
---------------+---+-----+-----
test_array_p1 | 1 | {1} | {1}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
tableoid | i | j | k
---------------+---+-------+-----
test_array_p2 | 2 | {2,2} | {2}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0

That's a bug. Fixed in the attached patch.

PS: I'm going to have limited Internet access during this weekend and over
the next week, so responses could be slow. Sorry about that.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-5.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-5.patchDownload
From 116cb85a500406843bff21138436e6f735025a34 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                    |  102 +++++++-
 doc/src/sgml/ref/create_table.sgml            |   55 ++++
 src/backend/access/common/reloptions.c        |    2 +
 src/backend/catalog/Makefile                  |    6 +-
 src/backend/catalog/aclchk.c                  |    2 +
 src/backend/catalog/dependency.c              |    2 +
 src/backend/catalog/heap.c                    |   27 ++-
 src/backend/catalog/objectaddress.c           |    5 +-
 src/backend/catalog/partition.c               |  394 +++++++++++++++++++++++++
 src/backend/catalog/pg_depend.c               |    3 +
 src/backend/catalog/pg_partitioned_table.c    |  172 +++++++++++
 src/backend/commands/analyze.c                |    2 +
 src/backend/commands/copy.c                   |    6 +
 src/backend/commands/indexcmds.c              |    7 +-
 src/backend/commands/lockcmds.c               |    2 +-
 src/backend/commands/policy.c                 |    2 +-
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/sequence.c               |    1 +
 src/backend/commands/tablecmds.c              |  383 +++++++++++++++++++++++-
 src/backend/commands/trigger.c                |    7 +-
 src/backend/commands/vacuum.c                 |    1 +
 src/backend/executor/execMain.c               |    2 +
 src/backend/executor/nodeModifyTable.c        |    1 +
 src/backend/nodes/copyfuncs.c                 |   33 ++
 src/backend/nodes/equalfuncs.c                |   28 ++
 src/backend/nodes/outfuncs.c                  |   26 ++
 src/backend/parser/gram.y                     |  110 ++++++--
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_expr.c               |    5 +
 src/backend/parser/parse_func.c               |    3 +
 src/backend/parser/parse_utilcmd.c            |   69 +++++
 src/backend/rewrite/rewriteDefine.c           |    1 +
 src/backend/rewrite/rewriteHandler.c          |    1 +
 src/backend/tcop/utility.c                    |    5 +-
 src/backend/utils/cache/relcache.c            |   19 ++-
 src/backend/utils/cache/syscache.c            |   12 +
 src/include/catalog/dependency.h              |    8 +-
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/partition.h               |   35 +++
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_partitioned_table.h    |   69 +++++
 src/include/catalog/pg_partitioned_table_fn.h |   29 ++
 src/include/commands/defrem.h                 |    2 +
 src/include/nodes/nodes.h                     |    2 +
 src/include/nodes/parsenodes.h                |   35 +++
 src/include/parser/kwlist.h                   |    1 +
 src/include/parser/parse_node.h               |    3 +-
 src/include/pg_config_manual.h                |    5 +
 src/include/utils/rel.h                       |    9 +
 src/include/utils/syscache.h                  |    1 +
 src/test/regress/expected/alter_table.out     |   46 +++
 src/test/regress/expected/create_table.out    |  159 ++++++++++
 src/test/regress/expected/sanity_check.out    |    1 +
 src/test/regress/sql/alter_table.sql          |   34 +++
 src/test/regress/sql/create_table.sql         |  137 +++++++++
 55 files changed, 2043 insertions(+), 45 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/backend/catalog/pg_partitioned_table.c
 create mode 100644 src/include/catalog/partition.h
 create mode 100644 src/include/catalog/pg_partitioned_table.h
 create mode 100644 src/include/catalog/pg_partitioned_table_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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>partedrelid</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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partkey</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns and/or expressions in the partition key and partition
+      rules associated with the partitions.
+     </para>
+
+     <para>
+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_type.o storage.o toasting.o pg_partitioned_table.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_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
 					 getObjectDescription(object));
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
+				Assert(foundDep->deptype != DEPENDENCY_IGNORE);
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
 					 foundDep->deptype, getObjectDescription(object));
 				subflags = 0;	/* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..9e040ed 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
+	/* Remove NO INHERIT flag if rel is a partitioned table */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
 	/*
 	 * Create the Check Constraint
 	 */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.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_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+	Oid		*typid;
+	int32	*typmod;
+	int16	*typlen;
+	bool	*typbyval;
+	char	*typalign;
+	Oid		*typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+	List	   *partexprs;		/* partition key expressions, if any */
+	char	  **partcolnames;	/* partition key column names */
+	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+								KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	int2vector	   *partattrs;
+	oidvector	   *opclass;
+	KeyTypeCollInfo *tcinfo;
+	ListCell	   *partexprbin_item;
+	List		   *partexprsrc = NIL;
+	ListCell	   *partexprsrc_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	/* Allocate in the supposedly short-lived working context */
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/* Open the catalog for its tuple descriptor */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	partattrs = (int2vector *) DatumGetPointer(datum);
+
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprbin,
+						 RelationGetDescr(catalog),
+						 &isnull);
+
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+
+		/* We should have a partexprsrc as well */
+		datum = heap_getattr(tuple,
+							 Anum_pg_partitioned_table_partexprsrc,
+							 RelationGetDescr(catalog),
+							 &isnull);
+		Assert(!isnull);
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+		partexprsrc = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+	tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+	tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather partition column names (simple C strings) */
+	key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+	/* Copy partattrs and fill other per-attribute info */
+	partexprbin_item = list_head(key->partexprs);
+	partexprsrc_item = list_head(partexprsrc);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		HeapTuple		tuple;
+		AttrNumber		attno;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		key->partattrs[i] = attno = partattrs->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+			tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+			tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+		}
+		get_typlenbyvalalign(tcinfo->typid[i],
+							 &tcinfo->typlen[i],
+							 &tcinfo->typbyval[i],
+							 &tcinfo->typalign[i]);
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+
+		/* Collect atttribute names */
+		if (key->partattrs[i] != 0)
+			key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+															key->partattrs[i]);
+		else
+		{
+			Value *str = lfirst(partexprsrc_item);
+			key->partcolnames[i] = pstrdup(str->val.str);
+			partexprsrc_item = lnext(partexprsrc_item);
+		}
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+	return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				i;
+
+	newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *)
+							palloc0(newkey->partnatts * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs,
+							newkey->partnatts * sizeof(AttrNumber));
+
+	newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype,
+							newkey->partnatts * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *)
+							palloc0(newkey->partnatts * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+							newkey->partnatts * sizeof(FmgrInfo));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+	newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+											 fromkey->tcinfo);
+	newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+	for (i = 0; i < newkey->partnatts; i++)
+		newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+	return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+	KeyTypeCollInfo   *result = (KeyTypeCollInfo *)
+								palloc0(sizeof(KeyTypeCollInfo));
+
+	result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+	result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+	memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+	result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+	memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+	result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+	result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+	memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+	result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+	memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+	return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
 
+	if (behavior == DEPENDENCY_IGNORE)
+		return;					/* nothing to do */
+
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
 
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..fa4d0f5
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ *	  routines to support manipulation of the pg_partitioned_table 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_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprbin,
+				  List *partexprsrc,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprbinDatum;
+	Datum		partexprsrcDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/*
+	 * Copy the partition key, opclass info into arrays (should we
+	 * make the caller pass them like this to start with?)
+	 */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprbin)
+	{
+		char       *exprbinString;
+		char       *exprsrcString;
+
+		exprbinString = nodeToString(partexprbin);
+		exprsrcString = nodeToString(partexprsrc);
+		partexprbinDatum = CStringGetTextDatum(exprbinString);
+		partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+		pfree(exprbinString);
+		pfree(exprsrcString);
+	}
+	else
+		partexprbinDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprbinDatum)
+	{
+		nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+		nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+	}
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+	values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprbin)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprbin,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_IGNORE);
+	/* Tell world about the key */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..04b60d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -262,6 +264,13 @@ struct DropRelationCallbackState
 	bool		concurrent;
 };
 
+/* for find_attr_reference_walker */
+typedef struct
+{
+	Oid			relid;
+	const char *attname;
+} find_attr_reference_context;
+
 /* Alter table target-type flags for ATSimplePermissions */
 #define		ATT_TABLE				0x0001
 #define		ATT_VIEW				0x0002
@@ -433,6 +442,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, const char *attname, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass);
 
 
 /* ----------------------------------------------------------------
@@ -596,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +713,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partby)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprbin = NIL;
+		List		   *partexprsrc = NIL;
+
+		stmt->partby = transformPartitionBy(rel, stmt->partby);
+		ComputePartitionAttrs(rel, stmt->partby->partParams,
+							  partattrs, &partexprbin, &partexprsrc,
+							  partopclass);
+
+		partnatts = list_length(stmt->partby->partParams);
+		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+						  partattrs, partexprbin, partexprsrc, partopclass);
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +990,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
+	 * RELKIND_RELATION while the relation is actually a partitioned table.
+	 */
+	if (classform->relkind != relkind &&
+				(relkind == RELKIND_RELATION &&
+					classform->relkind != RELKIND_PARTITIONED_TABLE))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1335,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1564,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5745,78 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_DropColumnRecurse;
 }
 
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var	   *var = (Var *) node;
+		char   *varattname = get_attname(context->relid, var->varattno);
+
+		if (!strcmp(varattname, context->attname))
+			return true;
+	}
+
+	return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, const char *attname, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+		char	   *partattname = get_partition_col_name(key, i);
+
+		if(partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (!strcmp(attname, partattname))
+				return true;
+		}
+		else
+		{
+			find_attr_reference_context context;
+
+			if (is_expr)
+				*is_expr = true;
+			context.relid = RelationGetRelid(rel);
+			context.attname = attname;
+			if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+				return true;
+
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
 /*
  * Return value is the address of the dropped column.
  */
@@ -5705,6 +5831,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5876,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, colName, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6407,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8007,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7891,6 +8038,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, colName, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7906,7 +8066,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8933,6 +9094,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9557,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9980,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10180,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10067,6 +10236,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11445,6 +11621,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12071,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12225,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12237,196 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+	PartitionBy	   *partby;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	partby = (PartitionBy *) makeNode(PartitionBy);
+
+	partby->strategy = partitionby->strategy;
+	partby->location = partitionby->location;
+	partby->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partitionby->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, partby->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+
+			/*
+			 * transformExpr() should have already rejected subqueries,
+			 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+			 * for an partition key expression.
+			 *
+			 * DefineRelation() will make more checks.
+			 */
+		}
+
+		partby->partParams = lappend(partby->partParams, pelem);
+	}
+
+	return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, List **partexprsrc,
+					  Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			if (IsA(expr, CollateExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot use COLLATE in partition key expression")));
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				char   *exprsrc;
+
+				partattrs[attn] = 0; /* marks expression */
+				*partexprbin = lappend(*partexprbin, expr);
+
+				/*
+				 * transformExpr() should have already rejected subqueries,
+				 * aggregates, and window functions, based on the EXPR_KIND_
+				 * for a partition key expression.
+				 */
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use a constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				exprsrc = deparse_expression(expr,
+							deparse_context_for(RelationGetRelationName(rel),
+												RelationGetRelid(rel)),
+									   false, false);
+				*partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+			}
+		}
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify an existing btree operator class or define one for the type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..7f79665 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+	PartitionBy *newnode = makeNode(PartitionBy);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5115,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionBy:
+			retval = _copyPartitionBy(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a3f990b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3409,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionBy:
+			retval = _equalPartitionBy(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 7e092d7..349d65f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3281,6 +3282,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3886,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionBy:
+				_outPartitionBy(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a95a65a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionBy			*partby;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partby>		PartitionBy OptPartitionBy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partby = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partby = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partby = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partby = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionBy *n = makeNode(PartitionBy);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..7f496ea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..d1de990 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..aef5e7f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partby != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partby)
+	{
+		int		partnatts = list_length(stmt->partby->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..f19479d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -983,10 +983,13 @@ ProcessUtilitySlow(ParseState *pstate,
 						{
 							Datum		toast_options;
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+							char	relkind = ((CreateStmt *) stmt)->partby != NULL
+													? RELKIND_PARTITIONED_TABLE
+													: RELKIND_RELATION;
 
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
-													 RELKIND_RELATION,
+													 relkind,
 													 InvalidOid, NULL);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
  * created only during initdb.  The fields for the dependent object
  * contain zeroes.
  *
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question.  However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
  * Other dependency flavors may be needed in future.
  */
 
@@ -77,7 +82,8 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
+	DEPENDENCY_IGNORE = 'g'
 } DependencyType;
 
 /*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* 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
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+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);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..db54358
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprbin;	/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+	pg_node_tree	partexprsrc;
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partexprbin	6
+#define Anum_pg_partitioned_table_partexprsrc	7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprbin,
+					List *partexprsrc,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif   /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionBy,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..2b50f8a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionBy
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionBy;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1787,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..99f68c7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..0af1422 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,162 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR:  cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..418ac55 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+	a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-5.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-5.patchDownload
From f63606c810b60bbeb5d9ee40af54d3f78282a6b2 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-5.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-5.patchDownload
From a705f53fd0ced82031be6c81e446d8f8d6498124 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 contrib/pg_trgm/trgm_op.c                  |    2 +-
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/heap.c                 |   35 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1714 +++++++++++++++++++++++++++-
 src/backend/catalog/pg_partitioned_table.c |    2 -
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/lockcmds.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  863 ++++++++++++--
 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             |    1 -
 src/backend/parser/parse_utilcmd.c         |  325 ++++++-
 src/backend/utils/cache/relcache.c         |  104 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   40 +
 src/include/catalog/pg_class.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            |    2 +-
 src/include/utils/rel.h                    |    9 +
 src/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  190 +++
 src/test/regress/sql/alter_table.sql       |  192 ++++
 src/test/regress/sql/create_table.sql      |  140 +++
 35 files changed, 4373 insertions(+), 168 deletions(-)

diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index dd0f492..1cfd341 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -1023,7 +1023,7 @@ trgm_presence_map(TRGM *query, TRGM *key)
 
 	result = (bool *) palloc0(lenq * sizeof(bool));
 
-	/* for each query trigram, do a binary search in the key array */
+/* for each query trigram, do a binary search in the key array */
 	for (i = 0; i < lenq; i++)
 	{
 		int			lo = 0;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1229,6 +1311,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..007782c 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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9e040ed..2fc404c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2054,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
-	/* Remove NO INHERIT flag if rel is a partitioned table */
+	/* Remove NO INHERIT flag if rel is a partitioned table or a partition */
 	if (is_no_inherit &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2475,7 +2494,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..2428aa8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * partition.c
- *        Partitioning related utility functions.
+ *        Partitioning related data structures and functions.
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
 #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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/ruleutils.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /* Type and collation information for partition key columns */
@@ -69,11 +77,151 @@ typedef struct PartitionKeyData
 	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
 } PartitionKeyData;
 
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
 								KeyTypeCollInfo *tcinfo);
 
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_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,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionRangeSpec *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionRangeSpec *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionListSpec *list);
+static List *get_qual_for_range(PartitionKey key, PartitionRangeSpec *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionListSpec *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
 /*
  * Partition key related functions
  */
@@ -392,3 +540,1567 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
 
 	return result;
 }
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionListSpec  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionListSpec));
+					list_spec = (PartitionListSpec *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->tcinfo->typbyval[0],
+													  key->tcinfo->typlen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionRangeSpec  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionRangeSpec));
+					range_spec = (PartitionRangeSpec *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				ListInfo *listinfo;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+
+				/*
+				 * Copy oids in the same order as they were found in the
+				 * pg_inherits catalog
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[i] = oids[i];
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				if (found_null_partition)
+					listinfo->null_index = null_partition_index;
+				else
+					listinfo->null_index = -1;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->tcinfo->typbyval[0],
+													key->tcinfo->typlen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+				}
+
+				result->bounds->listinfo = listinfo;
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			PartitionListSpec *list;
+
+			Assert(IsA(bound, PartitionListSpec));
+			list = (PartitionListSpec *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, 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("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+	Oid		parentOID = InvalidOid;
+	Relation rel;
+
+	rel = heap_open(relid, AccessShareLock);
+	if (rel->rd_rel->relispartition)
+		parentOID = get_partition_parent(relid);
+	heap_close(rel, AccessShareLock);
+
+	if (!OidIsValid(parentOID))
+		return NIL;
+
+	return list_concat(list_make1_oid(parentOID),
+					   get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionListSpec))
+	{
+		PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionRangeSpec))
+	{
+		PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionListSpec *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionRangeSpec *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionListSpec (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionListSpec *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1, key->partattrs[0],
+									key->tcinfo->typid[0],
+									key->tcinfo->typmod[0],
+									key->tcinfo->typcoll[0],
+									0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr. 
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->tcinfo->typid[0])
+							? get_array_type(key->tcinfo->typid[0])
+							: key->tcinfo->typid[0];
+	arr->array_collid = key->tcinfo->typcoll[0];
+	arr->element_typeid = key->tcinfo->typid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel)
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0], -1,
+							   get_typcollation(key->partopcintype[0]),
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->tcinfo->typcoll[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionRangeSpec (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_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 *nulltest;
+
+		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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  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	   *nulltest;
+
+		/* 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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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
+			 */
+			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 ge/gt lower_val */
+			strategy = 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 < 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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionListSpec
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										key->tcinfo->typbyval[0],
+										key->tcinfo->typlen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+	int	   *mapping;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+	/*
+	 * Index start from one.  A zero in nth slot means that partition n of
+	 * the first collection has not yet been mapped with a partition from
+	 * the second collection.
+	 */
+	mapping = (int *) palloc0((n + 1) * sizeof(int));
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		int		l1_partno = l1->indexes[i] + 1,
+				l2_partno = l2->indexes[i] + 1;
+
+		/* Encountered l1_partno for the first time, map to l2_partno */
+		if (mapping[l1_partno] == 0)
+			mapping[l1_partno] = l2_partno;
+		/*
+		 * Maintain that the mapping does not change, otherwise report
+		 * inequality
+		 */
+		else if (mapping[l1_partno] != l2_partno)
+			return false;
+	}
+
+	/* Check that nulls are accepted in mapped partitions */
+	Assert(l1->has_null || l1->null_index == -1);
+	Assert(l2->has_null || l2->null_index == -1);
+	if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+									  key->tcinfo->typcoll[0],
+									  val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionRangeSpec
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index fa4d0f5..453feb2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
 										RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
 										DEPENDENCY_IGNORE);
-	/* Tell world about the key */
-	CacheInvalidateRelcache(rel);
 
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 	heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 04b60d5..2f70eb7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -67,6 +67,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -164,6 +165,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 */
+	List	   *partition_quals; /* partition check quals 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 */
@@ -281,7 +284,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 +452,13 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprbin, List **partexprsrc,
 					  Oid *partopclass);
+static bool partattrs_overlap_ancestor_key(Relation rel, Oid parentId, int partnatts,
+							   AttrNumber *partattrs, List *partexprbin);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -486,6 +497,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -590,10 +602,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
@@ -668,6 +686,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -691,6 +727,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -726,8 +763,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ComputePartitionAttrs(rel, stmt->partby->partParams,
 							  partattrs, &partexprbin, &partexprsrc,
 							  partopclass);
-
 		partnatts = list_length(stmt->partby->partParams);
+
+		/*
+		 * If this is a partition, check that set of partition key columns
+		 * do not overlap those used by ancestors.
+		 */
+		if (stmt->partbound &&
+			partattrs_overlap_ancestor_key(rel, linitial_oid(inheritOids),
+										   partnatts, partattrs, partexprbin))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot use column or expression from ancestor partition key")));
+
 		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
 						  partattrs, partexprbin, partexprsrc, partopclass);
 	}
@@ -990,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
 	 * for OBJECT_TABLE relations.  It is ok for the passed in relkind to be
@@ -1075,7 +1130,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1453,7 +1509,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;
@@ -1498,8 +1555,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),
@@ -1526,6 +1583,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),
@@ -1561,18 +1630,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1710,6 +1798,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
 				def->is_from_type = false;
+				def->is_for_partition = false;
 				def->storage = attribute->attstorage;
 				def->raw_default = NULL;
 				def->cooked_default = NULL;
@@ -1838,6 +1927,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)
 	{
@@ -1867,16 +1959,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);
@@ -1913,8 +2009,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 */
@@ -2204,6 +2301,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2434,7 +2536,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2582,11 +2684,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2828,8 +2931,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3108,6 +3214,11 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
 				break;
 
+			case AT_AttachPartition:
+			case AT_DetachPartition:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
 			default:			/* oops */
 				elog(ERROR, "unrecognized alter table type: %d",
 					 (int) cmd->subtype);
@@ -3425,6 +3536,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);
@@ -3495,7 +3612,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3744,6 +3867,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);
@@ -3929,7 +4058,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4009,6 +4138,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4073,6 +4203,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4262,6 +4401,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4459,7 +4604,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4781,6 +4927,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5303,6 +5454,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5837,6 +6008,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5929,9 +6105,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6320,8 +6498,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7882,7 +8062,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);
@@ -7941,8 +8123,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8014,6 +8198,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10181,6 +10370,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10193,12 +10387,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;
 
@@ -10243,37 +10432,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10308,6 +10471,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);
 
@@ -10322,16 +10548,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;
 }
 
 /*
@@ -10382,7 +10600,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
@@ -10400,12 +10618,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10427,14 +10649,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10445,8 +10671,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10461,7 +10689,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10475,7 +10705,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.
@@ -10494,10 +10724,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10544,7 +10778,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10552,7 +10788,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10563,6 +10801,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10576,7 +10815,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10587,6 +10828,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10600,13 +10881,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];
@@ -10615,19 +10894,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
-
-	/*
-	 * 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);
+	bool		is_detach_partition = false;
 
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10637,7 +10907,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);
 
@@ -10658,11 +10928,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
@@ -10671,7 +10950,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)))
@@ -10733,7 +11012,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);
 
@@ -10764,7 +11043,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)
@@ -10776,30 +11055,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;
 }
 
 /*
@@ -12430,3 +12699,345 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		partopclass[attn++] = opclassOid;
 	}
 }
+
+/*
+ * Check if a new table's set of partition key columns overlaps that of any
+ * of its ancestors.
+ */
+static bool
+partattrs_overlap_ancestor_key(Relation rel, Oid parentId, int partnatts,
+							   AttrNumber *partattrs, List *partexprbin)
+{
+	bool		overlaps = false;
+	List	   *ancestors;
+	ListCell   *cell;
+	int			i;
+	ListCell   *expr_item;
+
+	ancestors = list_make1_oid(parentId);
+	ancestors = list_concat(ancestors, get_partition_ancestors(parentId));
+
+	expr_item = list_head(partexprbin);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber attno = partattrs[i];
+		List	  *attnames = NIL;
+
+		if (attno != 0)
+			attnames = lappend(attnames,
+							   get_attname(RelationGetRelid(rel), attno));
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(expr_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			pull_varattnos(expr, 1, &expr_attrs);
+			expr_item = lnext(expr_item);
+
+			index = -1;
+			while ((index = bms_next_member(expr_attrs, index)) > 0)
+			{
+				AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+				attnames = lappend(attnames,
+								   get_attname(RelationGetRelid(rel), attno));
+			}
+		}
+
+		foreach(cell, ancestors)
+		{
+			Relation	ancestor;
+			ListCell   *lc;
+
+			ancestor = heap_open(lfirst_oid(cell), AccessShareLock);
+
+			foreach(lc, attnames)
+			{
+				char	*name = (char *) lfirst(lc);
+
+				/*
+				 * Is the column a partition attribute in this ancestor table?
+				 */
+				if (is_partition_attr(ancestor, name, NULL))
+				{
+					overlaps = true;
+					break;
+				}
+			}
+
+			heap_close(ancestor, AccessShareLock);
+		}
+	}
+
+	return overlaps;
+}
+
+/*
+ * 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,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7f79665..ee30d48 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3018,6 +3019,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);
@@ -4201,6 +4203,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
  * ****************************************************************
@@ -5121,6 +5160,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 a3f990b..f811a6f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,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);
@@ -2376,6 +2377,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);
@@ -2656,6 +2658,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
  */
@@ -3415,6 +3448,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 349d65f..273bb72 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,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);
@@ -2587,6 +2588,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);
@@ -3303,6 +3305,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'
@@ -3892,6 +3913,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 917e6c8..fc3a599 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,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
@@ -2497,6 +2526,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 a95a65a..50657af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,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
@@ -601,7 +611,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11149,6 +11348,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;
@@ -13748,6 +13948,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13794,6 +13995,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13836,6 +14038,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..086de94 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index aef5e7f..f429a5a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,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);
 
 
 /*
@@ -231,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +258,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")));
@@ -360,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);
@@ -899,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;
@@ -1118,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;
@@ -2581,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2663,6 +2679,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;
@@ -3027,3 +3059,294 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	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 *) bound;
+			result_list = makeNode(PartitionListSpec);
+
+			foreach(cell, list->values)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\""
+							" of key column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+							 get_partition_col_name(key, 0)),
+					 parser_errposition(cxt->pstate, exprLocation(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 *) bound;
+
+			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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									get_partition_col_name(key, i)),
+							 parser_errposition(cxt->pstate, exprLocation(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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								get_partition_col_name(key, i)),
+							 parser_errposition(cxt->pstate, exprLocation(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 8cbd6e7..cd4f7f4 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);
 
 
 /*
@@ -925,6 +927,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1052,13 +1107,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2055,6 +2115,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2203,11 +2267,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2218,6 +2283,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);
@@ -2248,6 +2314,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
@@ -2303,6 +2372,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 */
@@ -3529,6 +3605,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5052,6 +5142,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..9ebba0c 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,10 +14,38 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "parser/parse_node.h"
 #include "utils/relcache.h"
 
 typedef struct PartitionKeyData *PartitionKey;
 
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
 
@@ -32,4 +60,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
 extern int32 get_partition_col_typmod(PartitionKey key, int col);
 extern char *get_partition_col_name(PartitionKey key, int col);
 
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 #endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,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 2b50f8a..69699e7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -734,6 +735,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
  ****************************************************************************/
@@ -1561,7 +1596,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
@@ -1786,7 +1823,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 99f68c7..0818d9c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,7 @@ 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 */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 91ba1be..b3e987d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -438,3 +438,193 @@ Table "public.describe_list_key"
 Partition Key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE fail_used_ancestor_key_col PARTITION OF parted FOR VALUES IN ('c') PARTITION BY LIST (lower(a));
+ERROR:  cannot use column or expression from ancestor partition key
+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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 b75542d..733c073 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,143 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE fail_used_ancestor_key_col PARTITION OF parted FOR VALUES IN ('c') PARTITION BY LIST (lower(a));
+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

0004-psql-and-pg_dump-support-for-partitions-5.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-5.patchDownload
From b285f28ba6e93a838f3173dc3e6ad1d67c5e55d0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++++----
 src/test/regress/expected/create_table.out |   39 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 ++++
 7 files changed, 393 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..4bc4e91 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list_spec = (PartitionListSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c805a84..40ab7cc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6134,6 +6134,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15311,6 +15368,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15339,8 +15407,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15369,7 +15440,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15434,15 +15506,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15612,6 +15691,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 10d924a..0b763ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b3e987d..fac621b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -598,6 +598,45 @@ ERROR:  cannot use column or expression from ancestor partition key
 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 733c073..5e45014 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -552,6 +552,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patchDownload
From 7e6a40b68583e9ec237dd2819bd1fdffd9d517c6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
 src/backend/optimizer/prep/prepunion.c |  278 ++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 2 files changed, 204 insertions(+), 83 deletions(-)

diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..193b2c9 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/* Do not flatten the inheritance hierarchy if partitioned table */
+	if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (childOID != parentOID)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
-		 */
-		if (oldrc)
-		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
-		}
-
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-5.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-5.patchDownload
From 03eeb16a971f26b59486a0a753a0928dc87c4905 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-5.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-5.patchDownload
From 6a745f60505455f976a4fc4bf7b0cbbb19d94add Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2428aa8..d341345 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -160,6 +160,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo					PartitionKey executor state
+ *
+ *	pdesc					Info about immediate partitions (see
+ *							PartitionDescData)
+ *
+ *	index					If a partition ourselves, index in the parent's
+ *							partition array
+ *
+ *	num_leaf_partitions		Number of leaf partitions in the partition
+ *							tree rooted at this node
+ *
+ *	offset					0-based index of the first leaf partition
+ *							in the partition tree rooted at this node
+ *
+ *	downlink				Link to our leftmost child node (ie, corresponding
+ *							to first of our partitions that is itself
+ *							partitioned)
+ *
+ *	next					Link to the right sibling node on a given level
+ *							(ie, corresponding to the next partition on the same
+ *							level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
@@ -201,6 +256,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -1090,6 +1149,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1736,6 +1842,106 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->parts[i]->oid;
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9ebba0c..07e6d64 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -45,6 +45,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
@@ -72,4 +73,8 @@ extern List *get_partition_ancestors(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-5.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-5.patchDownload
From b1e3e11967b46c9ce17e951908258e8ce0594f24 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  347 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 +++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 911 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index d341345..afc7d18 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -260,6 +260,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionListSpec *list_spec);
@@ -280,6 +292,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * Partition key related functions
@@ -1190,7 +1205,7 @@ get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
 			node = node->next;
 		}
 		else
-			result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
 	}
 
 	return result;
@@ -1858,7 +1873,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1876,7 +1891,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 	prev = NULL;
 	for (i = 0; i < parent->pdesc->nparts; i++)
 	{
-		Oid			relid = parent->pdesc->parts[i]->oid;
+		Oid			relid = parent->pdesc->oids[i];
 		int			offset;
 		Relation	rel;
 		PartitionTreeNode child;
@@ -1942,6 +1957,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = copy_partition_key(rel->rd_partkey);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -2310,3 +2586,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 157d219..932ed62 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1690,6 +1786,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2260,7 +2374,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2416,10 +2533,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2471,6 +2624,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2491,7 +2669,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2521,7 +2708,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2554,7 +2748,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2f70eb7..e6c206d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1251,6 +1251,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..32f4031 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6152,6 +6153,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 870fae3..5bdf03e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -798,8 +799,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 07e6d64..81a4b91 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/relcache.h"
 
@@ -77,4 +79,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 697c90f..6e51773 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1141,6 +1142,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-5.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-5.patchDownload
From f876649aa6a0b33f6d9e7bc4580aea5eb14d2f1a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f43352c..8084029 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#42Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#41)
Re: Declarative partitioning - another take

Hi Amit,

It looks like there is some problem while creating paramterized paths
for multi-level partitioned tables. Here's a longish testcase

CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES START
(1000) END (1100);
CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES START
(1100) END (1200);
INSERT INTO prt1_l SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
CREATE TABLE uprt1_l AS SELECT * FROM prt1_l;

CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (a);
CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((a + b));
CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES START
(1000) END (1100);
CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES START
(1100) END (1200);
INSERT INTO prt2_l SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 3) i;
CREATE TABLE uprt2_l AS SELECT * FROM prt2_l;

EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query

Let's replace the laterally referenced relation by an unpartitioned table.

EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query

Let's replace another partitioned table in the inner query with an
unpartitioned table.

EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN uprt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query

Please check if you are able to reproduce these errors in your
repository. I made sure that I cleaned up all partition-wise join code
before testing this, but ... .

I tried to debug the problem somewhat. In set_append_rel_pathlist(),
it finds that at least one child has a parameterized path as the
cheapest path, so it doesn't create an unparameterized path for append
rel. At the same time there is a parameterization common to all the
children, so it doesn't create any path. There seem to be two problems
here
1. The children from second level onwards may not be getting
parameterized for lateral references. That seems unlikely but
possible.
2. Reparameterization should have corrected this, but
reparameterize_path() does not support AppendPaths.

On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi

On 2016/09/09 17:55, Amit Langote wrote:

On 2016/09/06 22:04, Amit Langote wrote:

Will fix.

Here is an updated set of patches.

An email from Rajkumar somehow managed to break out of this thread.
Quoting his message below so that I don't end up replying with patches on
two different threads.

On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:

I have Continued with testing declarative partitioning with the latest
patch. Got some more observation, given below

Thanks a lot for testing.

-- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
partition.

create table test_range_bound ( a int) partition by range(a);
--creating a partition to contain records {1,2,3,4}, by default 1 is
inclusive and 5 is exclusive
create table test_range_bound_p1 partition of test_range_bound for values
start (1) end (5);
--now trying to create a partition by explicitly mentioning start is
exclusive to contain records {5,6,7}, here trying to create with START with
4 as exclusive so range should be 5 to 8, but getting partition overlap
error.
create table test_range_bound_p2 partition of test_range_bound for values
start (4) EXCLUSIVE end (8);
ERROR: partition "test_range_bound_p2" would overlap partition
"test_range_bound_p1"

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command. Range types do this
and hence an equivalent test done with range type values would disagree
with the result given by the patch for range partition bounds.

select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
cmp
-----
f
(1 row)

In this case, the second range is converted into its equivalent canonical
form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
ranges do not overlap after all. Range type operators can do this because
their code can rely on the availability of a canonicalization function for
a given range type. From the range types documentation:

"""
A discrete range type should have a canonicalization function that is
aware of the desired step size for the element type. The canonicalization
function is charged with converting equivalent values of the range type to
have identical representations, in particular consistently inclusive or
exclusive bounds. If a canonicalization function is not specified, then
ranges with different formatting will always be treated as unequal, even
though they might represent the same set of values in reality.
"""

to extend the last sentence:

"... or consider two ranges overlapping when in reality they are not
(maybe they are really just adjacent)."

Within the code handling range partition bound, no such canonicalization
happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
hence ranges overlap.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

One more option is we let the user specify the canonicalize function next
to the column name when defining the partition key. If not specified, we
hard-code one for the types for which we will be implementing a
canonicalize function (ie, above mentioned types). In other cases, we
just don't have one and hence if an unexpected result occurs when creating
a new partition, it's up to the user to realize what happened. Of course,
we will be mentioning in the documentation why a canonicalize function is
necessary and how to write one. Note that this canonicalize function
comes into play only when defining new partitions, it has no role beyond
that point.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

\d+ test_subpart
Table "public.test_subpart"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Key: RANGE (c1)
Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE

\d+ test_subpart_p1
Table "public.test_subpart_p1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
Partition Key: RANGE (c1)
Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)

insert into test_subpart values (50);
ERROR: no partition of relation "test_subpart_p1" found for row
DETAIL: Failing row contains (50).
insert into test_subpart values (150);
ERROR: no partition of relation "test_subpart" found for row
DETAIL: Failing row contains (150).

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.

CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');

INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);

postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
tableoid | i | j | k
---------------+---+-----+-----
test_array_p1 | 1 | {1} | {1}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
tableoid | i | j | k
---------------+---+-------+-----
test_array_p2 | 2 | {2,2} | {2}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0

That's a bug. Fixed in the attached patch.

PS: I'm going to have limited Internet access during this weekend and over
the next week, so responses could be slow. Sorry about that.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#43Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#41)
Re: Declarative partitioning - another take

On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command.

I think we shouldn't worry about this. It seems like unnecessary
scope creep. All human beings capable of using PostgreSQL will
understand that there are no integers between 4 and 5, so any
practical impact on this would be on someone creating partitions
automatically. But if someone is creating partitions automatically
they are highly likely to be using the same EXCLUSIVE/INCLUSIVE
settings for all of the partitions, in which case this won't arise.
And if they aren't, then I think we should just make them deal with
this limitation in their code instead of dealing with it in our code.
This patch is plenty complicated enough already; introducing a whole
new canonicalization concept that will help practically nobody seems
to me to be going in the wrong direction. If somebody really cares
enough to want to try to fix this, they can submit a followup patch
someday.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

-1. That is letting the tail wag the dog. Let's leave it the way you
had it and be happy.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

I again disagree. If somebody defines partition bounds that make it
impossible to insert the data that they care about, that's operator
error. The fact that, across multiple levels, you can manage to make
it impossible to insert any data at all does not make it anything
other than operator error. If we take the contrary position that it's
the system's job to prevent this sort of thing, we may disallow some
useful cases, like partitioning by the year portion of a date and then
subpartitioning by the month portion of that same date.

I think we'll probably also find that we're making the code
complicated to no purpose. For example, now you have to check when
attaching a partition that it doesn't violate the rule; otherwise you
end up with a table that can't be created directly (and thus can't
survive dump-and-restore) but can be created indirectly by exploiting
loopholes in the checks. It's tempting to think that we can check
simple cases - e.g. if the parent and the child are partitioning on
the same exact column, the child's range should be contained within
the parent's range - but more complicated cases are tricky. Suppose
the table is range-partitioned on (a, b) and range-subpartitioned on
b. It's not trivial to figure out whether the set of values that the
user can insert into that partition is non-empty. If we allow
partitioning on expressions, then it quickly becomes altogether
impossible to deduce anything useful - unless you can solve the
halting problem.

And, really, why do we care? If the user creates a partitioning
scheme that permits no rows in some or all of the partitions, then
they will have an empty table that can be correctly dumped and
restored but which cannot be used for anything useful unless it is
modified first. They probably don't want that, but it's not any more
broken than a table inheritance setup with mutually exclusive CHECK
constraints, or for that matter a plain table with mutually exclusive
CHECK constraints - and we don't try to prohibit those things. This
patch is supposed to be implementing partitioning, not artificial
intelligence.

-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.

postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0

That's a bug. Fixed in the attached patch.

Now on this one I'm not going to argue with your analysis. :-)

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

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

#44Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#43)
Re: Declarative partitioning - another take

On Thu, Sep 15, 2016 at 10:07 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command.

I think we shouldn't worry about this. It seems like unnecessary
scope creep. All human beings capable of using PostgreSQL will
understand that there are no integers between 4 and 5, so any
practical impact on this would be on someone creating partitions
automatically. But if someone is creating partitions automatically
they are highly likely to be using the same EXCLUSIVE/INCLUSIVE
settings for all of the partitions, in which case this won't arise.
And if they aren't, then I think we should just make them deal with
this limitation in their code instead of dealing with it in our code.
This patch is plenty complicated enough already; introducing a whole
new canonicalization concept that will help practically nobody seems
to me to be going in the wrong direction. If somebody really cares
enough to want to try to fix this, they can submit a followup patch
someday.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

-1. That is letting the tail wag the dog. Let's leave it the way you
had it and be happy.

Alright, let's leave this as something to work out later. We will
have to document the fact that such limitation exists though, I'd
think.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

I again disagree. If somebody defines partition bounds that make it
impossible to insert the data that they care about, that's operator
error. The fact that, across multiple levels, you can manage to make
it impossible to insert any data at all does not make it anything
other than operator error. If we take the contrary position that it's
the system's job to prevent this sort of thing, we may disallow some
useful cases, like partitioning by the year portion of a date and then
subpartitioning by the month portion of that same date.

I think we'll probably also find that we're making the code
complicated to no purpose. For example, now you have to check when
attaching a partition that it doesn't violate the rule; otherwise you
end up with a table that can't be created directly (and thus can't
survive dump-and-restore) but can be created indirectly by exploiting
loopholes in the checks. It's tempting to think that we can check
simple cases - e.g. if the parent and the child are partitioning on
the same exact column, the child's range should be contained within
the parent's range - but more complicated cases are tricky. Suppose
the table is range-partitioned on (a, b) and range-subpartitioned on
b. It's not trivial to figure out whether the set of values that the
user can insert into that partition is non-empty. If we allow
partitioning on expressions, then it quickly becomes altogether
impossible to deduce anything useful - unless you can solve the
halting problem.

And, really, why do we care? If the user creates a partitioning
scheme that permits no rows in some or all of the partitions, then
they will have an empty table that can be correctly dumped and
restored but which cannot be used for anything useful unless it is
modified first. They probably don't want that, but it's not any more
broken than a table inheritance setup with mutually exclusive CHECK
constraints, or for that matter a plain table with mutually exclusive
CHECK constraints - and we don't try to prohibit those things. This
patch is supposed to be implementing partitioning, not artificial
intelligence.

Agree with your arguments. Certainly, I overlooked some use cases
that my proposed solution would outright prevent. I withdraw it.

Thanks,
Amit

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

#45Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#3)
Re: Declarative partitioning - another take

On Mon, Aug 15, 2016 at 7:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

+    if (partexprs)
+        recordDependencyOnSingleRelExpr(&myself,
+                                        (Node *) partexprs,
+                                        RelationGetRelid(rel),
+                                        DEPENDENCY_NORMAL,
+                                        DEPENDENCY_IGNORE);

I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
here. Instead, you could just add an additional Boolean argument to
recordDependencyOnSingleRelExpr. That seems less likely to create
bugs in unrelated portions of the code.

I did consider a Boolean argument instead of a new DependencyType value,
however it felt a bit strange to pass a valid value for the fifth argument
(self_behavior) and then ask using a separate parameter that it (a
self-dependency) is to be ignored. By the way, no pg_depend entry is
created on such a call, so the effect of the new type's usage seems
localized to me. Thoughts?

I think that's not a very plausible argument. If you add a fifth
argument to that function, then only that function needs to know about
the possibility of ignoring self-dependencies. If you add a new
dependency type, then everything that knows about DependencyType needs
to know about them. That's got to be a much larger surface area for
bugs. Also, if you look around a bit, I believe you will find other
examples of cases where one argument is used only for certain values
of some other argument. That's not a novel design pattern.

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

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

#46Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#41)
Re: Declarative partitioning - another take

On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Re-reviewing 0001.

+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>

This documentation doesn't match pg_partition_table.h, which has
partexprsrc and partexprbin. I don't understand why it's a good idea
to have both, and there seem to be no comments or documentation
supporting that choice anywhere.

+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.

Both of the sentences in this paragraph that do not begin with "the"
need to begin with "the". (In my experience, it's generally a feature
of English as spoken in India that connecting words like "the" and "a"
are sometimes left out where non-Indian speakers of English would
include them, so it would be good to watch out for this issue in
general.)

Also, I think this should be rephrased a bit to be more clear about
how the partitioning key works, like this: The optional
<literal>PARTITION BY</literal> clause specifies a method of
partitioning the table. The table thus created is called a
<firstterm>partitioned</firstterm> table. The parenthesized list of
expressions forms the <firsttem>partitioning key</firstterm> for the
table. When using range partitioning, the partioning key can include
multiple columns or expressions, but for list partitioning, the
partitioning key must consist of a single column or expression. If no
btree operator class is specified when creating a partitioned table,
the default btree operator class for the datatype will be used. If
there is none, an error will be reported.

+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);

Why? None of the reloptions that pertain to heap seem relevant to a
relkind without storage.

But, ah, do partitioned tables have storage? I mean, do we end up
with an empty file, or no relfilenode at all? Can I CLUSTER, VACUUM,
etc. a partitioned table? It would seem cleaner for the parent to
have no relfilenode at all, but I'm guessing we might need some more
changes for that to work out.

+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\

Whitespace. Also, here and elsewhere, how about using alphabetical
order, or anyway preserving it insofar as the existing list is
alphabetized?

+    /* Remove NO INHERIT flag if rel is a partitioned table */
+    if (is_no_inherit &&
+        rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                 errmsg("cannot add NO INHERIT constraint to
partitioned table \"%s\"",
+                         RelationGetRelationName(rel))));

The code and the comment disagree. I think the code is right and the
comment should be adjusted to say something like /* Partitioned tables
do not have storage, so a NO INHERIT constraint makes no sense. */

+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c

Wrong.

+} KeyTypeCollInfo;

I don't like this type name very much. Can we get "Part" in there someplace?

It doesn't seem to be very well-designed, either. The number of
entries in each array is determined by the partnatts flag in
PartitionKeyData, which has also got various other arrays whose
lengths are determined by partnatts. Why do we have some arrays in
one structure and some arrays in another structure? Would it hurt
anything to merge everything into one structure? Or could
PartitionKeyData include a field of type KeyTypeCollInfo rather than
KeyTypeCollInfo *, saving one pointer reference every place we access
this data?

+ /* Allocate in the supposedly short-lived working context */

Why supposedly?

+    datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+                        RelationGetDescr(catalog),
+                        &isnull);

Isn't the point of putting the fixed-length fields first that we can
use GETSTRUCT() here? And even for partattrs as the first
variable-length thing?

+        /*
+         * Run the expressions through eval_const_expressions. This is
+         * not just an optimization, but is necessary, because eventually
+         * the planner will be comparing them to similarly-processed qual
+         * clauses, and may fail to detect valid matches without this.
+         * We don't bother with canonicalize_qual, however.
+         */

I'm a bit confused by this, because I would think this processing
ought to have been done before storing anything in the system
catalogs. I don't see why it should be necessary to do it again after
pulling data back out of the system catalogs.

+            Value *str = lfirst(partexprsrc_item);
+            key->partcolnames[i] = pstrdup(str->val.str);

Should have a blank line in between.

+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+    return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+    return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+    return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+    return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+    return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+    return key->tcinfo->typmod[col];
+}

If we're going to add notation for this, I think we should use macros
(or static inline functions defined in the header file). Doing it
this way adds more cycles for no benefit.

+    newkey->partattrs = (AttrNumber *)
+                            palloc0(newkey->partnatts * sizeof(AttrNumber));
+    memcpy(newkey->partattrs, fromkey->partattrs,
+                            newkey->partnatts * sizeof(AttrNumber));

It's wasteful to use palloc0 if you're immediately going to overwrite
every byte in the array. Use regular palloc instead.

+     * Copy the partition key, opclass info into arrays (should we
+     * make the caller pass them like this to start with?)

Only if it happens to be convenient for the caller, which doesn't seem
to be the case here.

+    /* Only this can ever be NULL */
+    if (!partexprbinDatum)
+    {
+        nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+        nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+    }

How can it be valid to have no partitioning expressions?

+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);

Is this really needed? Isn't the caller going to do something similar
pretty soon?

+ heap_freetuple(tuple);

Probably useless - might as well let the context reset clean it up.

+    simple_heap_delete(rel, &tuple->t_self);
+
+    /* Update the indexes on pg_partitioned_table */
+    CatalogUpdateIndexes(rel, tuple);

You don't need CatalogUpdateIndexes() after a delete, only after an
insert or update.

+    if (classform->relkind != relkind &&
+                (relkind == RELKIND_RELATION &&
+                    classform->relkind != RELKIND_PARTITIONED_TABLE))

That's broken. Note that all of the conditions are joined using &&,
so if any one of them fails then we won't throw an error. In
particular, it's no longer possible to throw an error when relkind is
not RELKIND_RELATION.

+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+    if (node == NULL)
+        return false;
+
+    if (IsA(node, Var))
+    {
+        Var       *var = (Var *) node;
+        char   *varattname = get_attname(context->relid, var->varattno);
+
+        if (!strcmp(varattname, context->attname))
+            return true;
+    }
+
+    return expression_tree_walker(node, find_attr_reference_walker, context);
+}

Hrm. The comment says we're matching on attnum, but the code says
we're matching on attname. is_partition_attr() has the same confusion
between comments and code. Maybe instead of this whole approach it
would be better to use pull_varattnos(), then get_attnum() to find the
attribute number for the one you want, then bms_is_member().

+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+    PartitionBy       *partby;
+    ParseState       *pstate;
+    RangeTblEntry  *rte;
+    ListCell       *l;
+
+    partby = (PartitionBy *) makeNode(PartitionBy);
+
+    partby->strategy = partitionby->strategy;
+    partby->location = partitionby->location;
+    partby->partParams = NIL;
+
+    /*
+     * Create a dummy ParseState and insert the target relation as its sole
+     * rangetable entry.  We need a ParseState for transformExpr.
+     */
+    pstate = make_parsestate(NULL);

Why isn't this logic being invoked from transformCreateStmt()? Then
we could use the actual parseState for the query instead of a fake
one.

+            if (IsA(expr, CollateExpr))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                         errmsg("cannot use COLLATE in partition key
expression")));

I assume there is a good reason for this seemingly-arbitrary
restriction, but there's no comment saying what it is. One thing
that's odd is that this will only prohibit a CollateExpr at the top
level, not in some more-deeply nested position. That seems
suspicious.

+                /*
+                 * User wrote "(column)" or "(column COLLATE something)".
+                 * Treat it like simple attribute anyway.
+                 */

Evidently, the user did not do that, because you just prohibited the
second one of those.

+                if (IsA(expr, Const))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("cannot use a constant expression
as partition key")));
+
+                if (contain_mutable_functions(expr))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("functions in partition key
expression must be marked IMMUTABLE")));

Do these checks parallel what we do for CHECK constraints? It might
be good to apply about the same level of rigor in both cases.

+                exprsrc = deparse_expression(expr,
+                            deparse_context_for(RelationGetRelationName(rel),
+                                                RelationGetRelid(rel)),
+                                       false, false);

Why bother? The output of this doesn't seem like a useful thing to
store. The fact that we've done similar things elsewhere doesn't make
it a good idea. I think we did it in other cases because we used to
be dumber than we are now.

+                        (errcode(ERRCODE_UNDEFINED_OBJECT),
+                         errmsg("data type %s has no default btree
operator class",
+                                format_type_be(atttype)),
+                         errhint("You must specify an existing btree
operator class or define one for the type.")));

The hint is not really accurate, because the type may well have a
btree operator class. Just not a default one.

+                            char    relkind = ((CreateStmt *)
stmt)->partby != NULL
+                                                    ? RELKIND_PARTITIONED_TABLE
+                                                    : RELKIND_RELATION;

Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
RELKIND_PARTITION_TABLE; }

+ RelationBuildPartitionKey(relation);

I wonder if RelationBuildPartitionKey should really be in relcache.c.
What do we do in similar cases?

+} PartitionBy;

Maybe PartitionSpec?

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

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

#47Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#41)
Re: Declarative partitioning - another take

Hi Amit,
Following sequence of DDLs gets an error
--
-- multi-leveled partitions
--
CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
ERROR: cannot use column or expression from ancestor partition key

The last statement is trying create subpartitions by range (b + a),
which contains a partition key from ancestor partition key but is not
exactly same as that. In fact it contains some extra columns other
than the ancestor partition key columns. Why do we want to prohibit
such cases?

On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi

On 2016/09/09 17:55, Amit Langote wrote:

On 2016/09/06 22:04, Amit Langote wrote:

Will fix.

Here is an updated set of patches.

An email from Rajkumar somehow managed to break out of this thread.
Quoting his message below so that I don't end up replying with patches on
two different threads.

On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:

I have Continued with testing declarative partitioning with the latest
patch. Got some more observation, given below

Thanks a lot for testing.

-- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
partition.

create table test_range_bound ( a int) partition by range(a);
--creating a partition to contain records {1,2,3,4}, by default 1 is
inclusive and 5 is exclusive
create table test_range_bound_p1 partition of test_range_bound for values
start (1) end (5);
--now trying to create a partition by explicitly mentioning start is
exclusive to contain records {5,6,7}, here trying to create with START with
4 as exclusive so range should be 5 to 8, but getting partition overlap
error.
create table test_range_bound_p2 partition of test_range_bound for values
start (4) EXCLUSIVE end (8);
ERROR: partition "test_range_bound_p2" would overlap partition
"test_range_bound_p1"

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command. Range types do this
and hence an equivalent test done with range type values would disagree
with the result given by the patch for range partition bounds.

select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
cmp
-----
f
(1 row)

In this case, the second range is converted into its equivalent canonical
form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
ranges do not overlap after all. Range type operators can do this because
their code can rely on the availability of a canonicalization function for
a given range type. From the range types documentation:

"""
A discrete range type should have a canonicalization function that is
aware of the desired step size for the element type. The canonicalization
function is charged with converting equivalent values of the range type to
have identical representations, in particular consistently inclusive or
exclusive bounds. If a canonicalization function is not specified, then
ranges with different formatting will always be treated as unequal, even
though they might represent the same set of values in reality.
"""

to extend the last sentence:

"... or consider two ranges overlapping when in reality they are not
(maybe they are really just adjacent)."

Within the code handling range partition bound, no such canonicalization
happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
hence ranges overlap.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

One more option is we let the user specify the canonicalize function next
to the column name when defining the partition key. If not specified, we
hard-code one for the types for which we will be implementing a
canonicalize function (ie, above mentioned types). In other cases, we
just don't have one and hence if an unexpected result occurs when creating
a new partition, it's up to the user to realize what happened. Of course,
we will be mentioning in the documentation why a canonicalize function is
necessary and how to write one. Note that this canonicalize function
comes into play only when defining new partitions, it has no role beyond
that point.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

\d+ test_subpart
Table "public.test_subpart"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Key: RANGE (c1)
Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE

\d+ test_subpart_p1
Table "public.test_subpart_p1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
Partition Key: RANGE (c1)
Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)

insert into test_subpart values (50);
ERROR: no partition of relation "test_subpart_p1" found for row
DETAIL: Failing row contains (50).
insert into test_subpart values (150);
ERROR: no partition of relation "test_subpart" found for row
DETAIL: Failing row contains (150).

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.

CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');

INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);

postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
tableoid | i | j | k
---------------+---+-----+-----
test_array_p1 | 1 | {1} | {1}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
tableoid | i | j | k
---------------+---+-------+-----
test_array_p2 | 2 | {2,2} | {2}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0

That's a bug. Fixed in the attached patch.

PS: I'm going to have limited Internet access during this weekend and over
the next week, so responses could be slow. Sorry about that.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#48Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Ashutosh Bapat (#47)
1 attachment(s)
Re: Declarative partitioning - another take

For list partitions, the ListInfo stores the index maps for values
i.e. the index of the partition to which the value belongs. Those
indexes are same as the indexes in partition OIDs array and come from
the catalogs. In case a user creates two partitioned tables with
exactly same lists for partitions but specifies them in a different
order, the OIDs are stored in the order specified. This means that
index array for these tables come out different. equal_list_info()
works around that by creating an array of mappings and checks whether
that mapping is consistent for all values. This means we will create
the mapping as many times as equal_list_info() is called, which is
expected to be more than the number of time
RelationBuildPartitionDescriptor() is called. Instead, if we
"canonicalise" the indexes so that they come out exactly same for
similarly partitioned tables, we build the mapping only once and
arrange OIDs accordingly.

Here's patch to do that. I have ran make check with this and it didn't
show any failure. Please consider this to be included in your next set
of patches.

That helps partition-wise join as well. For partition-wise join (and
further optimizations for partitioned tables), we create a list of
canonical partition schemes. In this list two similarly partitioned
tables share partition scheme pointer. A join between relations with
same partition scheme pointer can be joined partition-wise. It's
important that the indexes in partition scheme match to the OIDs array
to find matching RelOptInfos for partition-wise join.

On Thu, Sep 22, 2016 at 11:12 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Hi Amit,
Following sequence of DDLs gets an error
--
-- multi-leveled partitions
--
CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
ERROR: cannot use column or expression from ancestor partition key

The last statement is trying create subpartitions by range (b + a),
which contains a partition key from ancestor partition key but is not
exactly same as that. In fact it contains some extra columns other
than the ancestor partition key columns. Why do we want to prohibit
such cases?

On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi

On 2016/09/09 17:55, Amit Langote wrote:

On 2016/09/06 22:04, Amit Langote wrote:

Will fix.

Here is an updated set of patches.

An email from Rajkumar somehow managed to break out of this thread.
Quoting his message below so that I don't end up replying with patches on
two different threads.

On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:

I have Continued with testing declarative partitioning with the latest
patch. Got some more observation, given below

Thanks a lot for testing.

-- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
partition.

create table test_range_bound ( a int) partition by range(a);
--creating a partition to contain records {1,2,3,4}, by default 1 is
inclusive and 5 is exclusive
create table test_range_bound_p1 partition of test_range_bound for values
start (1) end (5);
--now trying to create a partition by explicitly mentioning start is
exclusive to contain records {5,6,7}, here trying to create with START with
4 as exclusive so range should be 5 to 8, but getting partition overlap
error.
create table test_range_bound_p2 partition of test_range_bound for values
start (4) EXCLUSIVE end (8);
ERROR: partition "test_range_bound_p2" would overlap partition
"test_range_bound_p1"

Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command. Range types do this
and hence an equivalent test done with range type values would disagree
with the result given by the patch for range partition bounds.

select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
cmp
-----
f
(1 row)

In this case, the second range is converted into its equivalent canonical
form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
ranges do not overlap after all. Range type operators can do this because
their code can rely on the availability of a canonicalization function for
a given range type. From the range types documentation:

"""
A discrete range type should have a canonicalization function that is
aware of the desired step size for the element type. The canonicalization
function is charged with converting equivalent values of the range type to
have identical representations, in particular consistently inclusive or
exclusive bounds. If a canonicalization function is not specified, then
ranges with different formatting will always be treated as unequal, even
though they might represent the same set of values in reality.
"""

to extend the last sentence:

"... or consider two ranges overlapping when in reality they are not
(maybe they are really just adjacent)."

Within the code handling range partition bound, no such canonicalization
happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
hence ranges overlap.

To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.

One more option is we let the user specify the canonicalize function next
to the column name when defining the partition key. If not specified, we
hard-code one for the types for which we will be implementing a
canonicalize function (ie, above mentioned types). In other cases, we
just don't have one and hence if an unexpected result occurs when creating
a new partition, it's up to the user to realize what happened. Of course,
we will be mentioning in the documentation why a canonicalize function is
necessary and how to write one. Note that this canonicalize function
comes into play only when defining new partitions, it has no role beyond
that point.

-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.

create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);

\d+ test_subpart
Table "public.test_subpart"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Key: RANGE (c1)
Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE

\d+ test_subpart_p1
Table "public.test_subpart_p1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
Partition Key: RANGE (c1)
Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)

insert into test_subpart values (50);
ERROR: no partition of relation "test_subpart_p1" found for row
DETAIL: Failing row contains (50).
insert into test_subpart values (150);
ERROR: no partition of relation "test_subpart" found for row
DETAIL: Failing row contains (150).

It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.

-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.

CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');

INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);

postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
tableoid | i | j | k
---------------+---+-----+-----
test_array_p1 | 1 | {1} | {1}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
tableoid | i | j | k
---------------+---+-------+-----
test_array_p2 | 2 | {2,2} | {2}
(1 row)

postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0

That's a bug. Fixed in the attached patch.

PS: I'm going to have limited Internet access during this weekend and over
the next week, so responses could be slow. Sorry about that.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

Attachments:

canonical_partition_indexes.patchinvalid/octet-stream; name=canonical_partition_indexes.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index afc7d18..ccb8fde 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -785,15 +785,14 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRAT_LIST:
 			{
 				ListInfo *listinfo;
+				int	   *mappings = (int *)palloc(sizeof(int) * nparts);
+				int		next_index = 0;
 
 				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
 
-				/*
-				 * Copy oids in the same order as they were found in the
-				 * pg_inherits catalog
-				 */
+				/* Invalid mapping. */
 				for (i = 0; i < nparts; i++)
-					result->oids[i] = oids[i];
+					mappings[i] = -1;
 
 				/* Sort so that we can perform binary search over values */
 				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
@@ -801,23 +800,48 @@ RelationBuildPartitionDesc(Relation rel)
 
 				listinfo->nvalues = all_values_count;
 				listinfo->has_null = found_null_partition;
-				if (found_null_partition)
-					listinfo->null_index = null_partition_index;
-				else
-					listinfo->null_index = -1;
 				listinfo->values = (Datum *)
 									palloc0(all_values_count * sizeof(Datum));
 				listinfo->indexes = (int *)
 									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * While copying the indexes, adjust them so that they are same
+				 * for any two partitioning schemes with the same lists. One
+				 * way to achieve this is to assign indexes in the same order
+				 * as the least values in the lists.
+				 */
 				for (i = 0; i < all_values_count; i++)
 				{
 					listinfo->values[i] = datumCopy(all_values[i]->value,
 													key->tcinfo->typbyval[0],
 													key->tcinfo->typlen[0]);
-					listinfo->indexes[i] = all_values[i]->index;
+
+					/* If this index has no mapping assign one. */
+					if (mappings[all_values[i]->index] == -1)
+						mappings[all_values[i]->index] = next_index++;
+
+					listinfo->indexes[i] = mappings[all_values[i]->index];
 				}
 
+				/* Assign a valid mapping to partition with NULLL values. */
+				if (mappings[null_partition_index] == -1)
+					mappings[null_partition_index] = next_index++;
+
+				/* All partitions get a valid mapping. */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo->null_index = mappings[null_partition_index];
+				else
+					listinfo->null_index = -1;
+
+				/* Re-arrange partition OIDs as per mappings. */
+				for (i = 0; i < nparts; i++)
+					result->oids[i] = oids[mappings[i]];
+
 				result->bounds->listinfo = listinfo;
+				pfree(mappings);
 				break;
 			}
 
@@ -2273,7 +2297,6 @@ static bool
 equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
 {
 	int		i;
-	int	   *mapping;
 
 	if (l1->nvalues != l2->nvalues)
 		return false;
@@ -2282,35 +2305,15 @@ equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
 		return false;
 
 	for (i = 0; i < l1->nvalues; i++)
+	{
 		if (list_values_cmp(key, l1->values[i], l2->values[i]))
 			return false;
 
-	/*
-	 * Index start from one.  A zero in nth slot means that partition n of
-	 * the first collection has not yet been mapped with a partition from
-	 * the second collection.
-	 */
-	mapping = (int *) palloc0((n + 1) * sizeof(int));
-	for (i = 0; i < l1->nvalues; i++)
-	{
-		int		l1_partno = l1->indexes[i] + 1,
-				l2_partno = l2->indexes[i] + 1;
-
-		/* Encountered l1_partno for the first time, map to l2_partno */
-		if (mapping[l1_partno] == 0)
-			mapping[l1_partno] = l2_partno;
-		/*
-		 * Maintain that the mapping does not change, otherwise report
-		 * inequality
-		 */
-		else if (mapping[l1_partno] != l2_partno)
-			return false;
+		/* Make sure that the indexes are "canonicalised. */
+		Assert(l1->indexes[i] == l2->indexes[i]);
 	}
 
-	/* Check that nulls are accepted in mapped partitions */
-	Assert(l1->has_null || l1->null_index == -1);
-	Assert(l2->has_null || l2->null_index == -1);
-	if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+	if (l1->null_index != l2->null_index);
 		return false;
 
 	return true;
#49Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Ashutosh Bapat (#48)
Re: Declarative partitioning - another take

On Thu, Sep 22, 2016 at 1:02 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

For list partitions, the ListInfo stores the index maps for values
i.e. the index of the partition to which the value belongs. Those
indexes are same as the indexes in partition OIDs array and come from
the catalogs. In case a user creates two partitioned tables with
exactly same lists for partitions but specifies them in a different
order, the OIDs are stored in the order specified. This means that
index array for these tables come out different. equal_list_info()
works around that by creating an array of mappings and checks whether
that mapping is consistent for all values. This means we will create
the mapping as many times as equal_list_info() is called, which is
expected to be more than the number of time
RelationBuildPartitionDescriptor() is called. Instead, if we
"canonicalise" the indexes so that they come out exactly same for
similarly partitioned tables, we build the mapping only once and
arrange OIDs accordingly.

Here's patch to do that. I have ran make check with this and it didn't
show any failure. Please consider this to be included in your next set
of patches.

The patch has an if condition as statement by itself
+ if (l1->null_index != l2->null_index);
return false;

There shouldn't be ';' at the end. It looks like in the tests you have
added the function always bails out before it reaches this statement.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#50Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#47)
Re: Declarative partitioning - another take

Hi Ashutosh,

On 2016/09/22 14:42, Ashutosh Bapat wrote:

Hi Amit,
Following sequence of DDLs gets an error
--
-- multi-leveled partitions
--
CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
ERROR: cannot use column or expression from ancestor partition key

The last statement is trying create subpartitions by range (b + a),
which contains a partition key from ancestor partition key but is not
exactly same as that. In fact it contains some extra columns other
than the ancestor partition key columns. Why do we want to prohibit
such cases?

Per discussion [1]/messages/by-id/CA+HiwqEXAU_m+V=b-VGmsDNjoqc-Z_9KQdyPuOGbiQGzNObmVg@mail.gmail.com, I am going to remove this ill-considered restriction.

Thanks,
Amit

[1]: /messages/by-id/CA+HiwqEXAU_m+V=b-VGmsDNjoqc-Z_9KQdyPuOGbiQGzNObmVg@mail.gmail.com
/messages/by-id/CA+HiwqEXAU_m+V=b-VGmsDNjoqc-Z_9KQdyPuOGbiQGzNObmVg@mail.gmail.com

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

#51Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#49)
Re: Declarative partitioning - another take

On 2016/09/22 19:10, Ashutosh Bapat wrote:

On Thu, Sep 22, 2016 at 1:02 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

For list partitions, the ListInfo stores the index maps for values
i.e. the index of the partition to which the value belongs. Those
indexes are same as the indexes in partition OIDs array and come from
the catalogs. In case a user creates two partitioned tables with
exactly same lists for partitions but specifies them in a different
order, the OIDs are stored in the order specified. This means that
index array for these tables come out different. equal_list_info()
works around that by creating an array of mappings and checks whether
that mapping is consistent for all values. This means we will create
the mapping as many times as equal_list_info() is called, which is
expected to be more than the number of time
RelationBuildPartitionDescriptor() is called. Instead, if we
"canonicalise" the indexes so that they come out exactly same for
similarly partitioned tables, we build the mapping only once and
arrange OIDs accordingly.

Here's patch to do that. I have ran make check with this and it didn't
show any failure. Please consider this to be included in your next set
of patches.

Thanks. It seems like this will save quite a few cycles for future users
of equal_list_info(). I will incorporate it into may patch.

With this patch, the mapping is created *only once* during
RelationBuildPartitionDesc() to assign canonical indexes to individual
list values. The partition OID array will also be rearranged such that
using the new (canonical) index instead of the old
catalog-scan-order-based index will retrieve the correct partition for
that value.

By the way, I fixed one thinko in your patch as follows:

-        result->oids[i] = oids[mapping[i]];
+        result->oids[mapping[i]] = oids[i];

That is, copy an OID at a given position in the original array to a
*mapped position* in the result array (instead of the other way around).

The patch has an if condition as statement by itself
+ if (l1->null_index != l2->null_index);
return false;

There shouldn't be ';' at the end. It looks like in the tests you have
added the function always bails out before it reaches this statement.

There is no user of equal_list_info() such that the above bug would have
caused a regression test failure. Maybe a segfault crash (due to dangling
pointer into partition descriptor's listinfo after the above function
would unintentionally return false to equalPartitionDescs() which is in
turn called by RelationClearRelation()).

Thanks,
Amit

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

#52Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#51)
Re: Declarative partitioning - another take

With this patch, the mapping is created *only once* during
RelationBuildPartitionDesc() to assign canonical indexes to individual
list values. The partition OID array will also be rearranged such that
using the new (canonical) index instead of the old
catalog-scan-order-based index will retrieve the correct partition for
that value.

By the way, I fixed one thinko in your patch as follows:

-        result->oids[i] = oids[mapping[i]];
+        result->oids[mapping[i]] = oids[i];

While I can not spot any problem with this logic, when I make that
change and run partition_join testcase in my patch, it fails because
wrong partitions are matched for partition-wise join of list
partitions. In that patch, RelOptInfo of partitions are saved in
RelOptInfo of the parent by matching their OIDs. They are saved in the
same order as corresponding OIDs. Partition-wise join simply joins the
RelOptInfos at the same positions from both the parent RelOptInfos. I
can not spot an error in this logic too.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#53Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#42)
1 attachment(s)
Re: Declarative partitioning - another take

Sorry about the delay in replying.

On 2016/09/15 21:58, Ashutosh Bapat wrote:

Hi Amit,

It looks like there is some problem while creating paramterized paths
for multi-level partitioned tables. Here's a longish testcase

[ ... ]

Please check if you are able to reproduce these errors in your
repository. I made sure that I cleaned up all partition-wise join code
before testing this, but ... .

Thanks for the test case. I can reproduce the same.

I tried to debug the problem somewhat. In set_append_rel_pathlist(),
it finds that at least one child has a parameterized path as the
cheapest path, so it doesn't create an unparameterized path for append
rel. At the same time there is a parameterization common to all the
children, so it doesn't create any path. There seem to be two problems
here
1. The children from second level onwards may not be getting
parameterized for lateral references. That seems unlikely but
possible.
2. Reparameterization should have corrected this, but
reparameterize_path() does not support AppendPaths.

Hmm, 0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch is
certainly to be blamed here; if I revert the patch, the problem goes away.

Based on 2 above, I attempted to add logic for AppendPath in
reparameterize_path() as in the attached. It fixes the reported problem
and does not break any regression tests. If it's sane to do things this
way, I will incorporate the attached into patch 0005 mentioned above.
Thoughts?

Thanks,
Amit

Attachments:

reparameterize-append-path.patchtext/x-diff; name=reparameterize-append-path.patchDownload
commit 86c8fc2c268d17fb598bf6879cfef2b2a2f42417
Author: amit <amitlangote09@gmail.com>
Date:   Tue Sep 27 16:02:54 2016 +0900

    Handle AppendPath in reparameterize_path().

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 2a49639..750f0ea 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1570,6 +1570,40 @@ cost_sort(Path *path, PlannerInfo *root,
 }
 
 /*
+ * cost_append
+ *	  Determines and returns the cost of a Append node
+ *
+ * Compute rows and costs as sums of subplan rows and costs.  We charge
+ * nothing extra for the Append itself, which perhaps is too optimistic,
+ * but since it doesn't do any selection or projection, it is a pretty
+ * cheap node.
+ */
+void
+cost_append(AppendPath *apath, Relids required_outer)
+{
+	ListCell   *l;
+
+	apath->path.rows = 0;
+	apath->path.startup_cost = 0;
+	apath->path.total_cost = 0;
+	foreach(l, apath->subpaths)
+	{
+		Path	   *subpath = (Path *) lfirst(l);
+
+		apath->path.rows += subpath->rows;
+
+		if (l == list_head(apath->subpaths))	/* first node? */
+			apath->path.startup_cost = subpath->startup_cost;
+		apath->path.total_cost += subpath->total_cost;
+		apath->path.parallel_safe = apath->path.parallel_safe &&
+			subpath->parallel_safe;
+
+		/* All child paths must have same parameterization */
+		Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
+	}
+}
+
+/*
  * cost_merge_append
  *	  Determines and returns the cost of a MergeAppend node.
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index abb7507..bf8fb55 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1206,7 +1206,6 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
 				   int parallel_workers)
 {
 	AppendPath *pathnode = makeNode(AppendPath);
-	ListCell   *l;
 
 	pathnode->path.pathtype = T_Append;
 	pathnode->path.parent = rel;
@@ -1220,32 +1219,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
 										 * unsorted */
 	pathnode->subpaths = subpaths;
 
-	/*
-	 * We don't bother with inventing a cost_append(), but just do it here.
-	 *
-	 * Compute rows and costs as sums of subplan rows and costs.  We charge
-	 * nothing extra for the Append itself, which perhaps is too optimistic,
-	 * but since it doesn't do any selection or projection, it is a pretty
-	 * cheap node.
-	 */
-	pathnode->path.rows = 0;
-	pathnode->path.startup_cost = 0;
-	pathnode->path.total_cost = 0;
-	foreach(l, subpaths)
-	{
-		Path	   *subpath = (Path *) lfirst(l);
-
-		pathnode->path.rows += subpath->rows;
-
-		if (l == list_head(subpaths))	/* first node? */
-			pathnode->path.startup_cost = subpath->startup_cost;
-		pathnode->path.total_cost += subpath->total_cost;
-		pathnode->path.parallel_safe = pathnode->path.parallel_safe &&
-			subpath->parallel_safe;
-
-		/* All child paths must have same parameterization */
-		Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
-	}
+	cost_append(pathnode, required_outer);
 
 	return pathnode;
 }
@@ -3204,6 +3178,41 @@ reparameterize_path(PlannerInfo *root, Path *path,
 														 spath->path.pathkeys,
 														 required_outer);
 			}
+		case T_Append:
+			{
+				AppendPath	*apath = (AppendPath *) path;
+				AppendPath	*newpath;
+				List		*new_subpaths = NIL;
+				ListCell	*lc;
+
+				foreach(lc, apath->subpaths)
+				{
+					Path	*subpath = lfirst(lc);
+					Path	*new_subpath;
+
+					new_subpath = reparameterize_path(root, subpath,
+													  required_outer,
+													  loop_count);
+					if (new_subpath)
+						new_subpaths = lappend(new_subpaths, new_subpath);
+					else
+						return NULL;
+				}
+
+				/*
+				 * Like IndexPath, we flat-copy the path node, and revise
+				 * its param_info, and redo the cost estimate (costs of
+				 * subpaths may have changed due to parameterization).
+				 */
+				newpath = makeNode(AppendPath);
+				memcpy(newpath, apath, sizeof(AppendPath));
+				newpath->path.param_info =
+					get_appendrel_parampathinfo(rel, required_outer);
+				newpath->subpaths = new_subpaths;
+				cost_append(newpath, required_outer);
+
+				return (Path *) newpath;
+			}
 		default:
 			break;
 	}
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 2a4df2f..ec1d6f2 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -98,6 +98,7 @@ extern void cost_sort(Path *path, PlannerInfo *root,
 		  List *pathkeys, Cost input_cost, double tuples, int width,
 		  Cost comparison_cost, int sort_mem,
 		  double limit_tuples);
+extern void cost_append(AppendPath *apath, Relids required_outer);
 extern void cost_merge_append(Path *path, PlannerInfo *root,
 				  List *pathkeys, int n_streams,
 				  Cost input_startup_cost, Cost input_total_cost,
#54Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#53)
Re: Declarative partitioning - another take

Please check if you are able to reproduce these errors in your
repository. I made sure that I cleaned up all partition-wise join code
before testing this, but ... .

Thanks for the test case. I can reproduce the same.

I tried to debug the problem somewhat. In set_append_rel_pathlist(),
it finds that at least one child has a parameterized path as the
cheapest path, so it doesn't create an unparameterized path for append
rel. At the same time there is a parameterization common to all the
children, so it doesn't create any path. There seem to be two problems
here
1. The children from second level onwards may not be getting
parameterized for lateral references. That seems unlikely but
possible.

Did you check this? We may be missing on creating index scan paths
with parameterization. If we fix this, we don't need to
re-parameterize Append.

2. Reparameterization should have corrected this, but
reparameterize_path() does not support AppendPaths.

Hmm, 0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch is
certainly to be blamed here; if I revert the patch, the problem goes away.

Based on 2 above, I attempted to add logic for AppendPath in
reparameterize_path() as in the attached. It fixes the reported problem
and does not break any regression tests. If it's sane to do things this
way, I will incorporate the attached into patch 0005 mentioned above.
Thoughts?

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#55Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#52)
Re: Declarative partitioning - another take

On 2016/09/27 15:44, Ashutosh Bapat wrote:

By the way, I fixed one thinko in your patch as follows:

-        result->oids[i] = oids[mapping[i]];
+        result->oids[mapping[i]] = oids[i];

While I can not spot any problem with this logic, when I make that
change and run partition_join testcase in my patch, it fails because
wrong partitions are matched for partition-wise join of list
partitions. In that patch, RelOptInfo of partitions are saved in
RelOptInfo of the parent by matching their OIDs. They are saved in the
same order as corresponding OIDs. Partition-wise join simply joins the
RelOptInfos at the same positions from both the parent RelOptInfos. I
can not spot an error in this logic too.

OTOH, using the original logic makes tuple routing put tuples into the
wrong partitions. When debugging why that was happening I discovered this
and hence the proposed change.

You mean that partition RelOptInfo's are placed using the canonical
ordering of OIDs instead of catalog-scan-driven order, right? If that's
true, then there is no possibility of wrong pairing happening, even with
the new ordering of OIDs in the partition descriptor (ie, the ordering
that would be produced by my proposed method above).

Thanks,
Amit

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

#56Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#55)
Re: Declarative partitioning - another take

On Tue, Sep 27, 2016 at 2:46 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/09/27 15:44, Ashutosh Bapat wrote:

By the way, I fixed one thinko in your patch as follows:

-        result->oids[i] = oids[mapping[i]];
+        result->oids[mapping[i]] = oids[i];

While I can not spot any problem with this logic, when I make that
change and run partition_join testcase in my patch, it fails because
wrong partitions are matched for partition-wise join of list
partitions. In that patch, RelOptInfo of partitions are saved in
RelOptInfo of the parent by matching their OIDs. They are saved in the
same order as corresponding OIDs. Partition-wise join simply joins the
RelOptInfos at the same positions from both the parent RelOptInfos. I
can not spot an error in this logic too.

OTOH, using the original logic makes tuple routing put tuples into the
wrong partitions. When debugging why that was happening I discovered this
and hence the proposed change.

You mean that partition RelOptInfo's are placed using the canonical
ordering of OIDs instead of catalog-scan-driven order, right? If that's
true, then there is no possibility of wrong pairing happening, even with
the new ordering of OIDs in the partition descriptor (ie, the ordering
that would be produced by my proposed method above).

right! I don't know what's wrong, will debug my changes.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#57Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#54)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/09/27 18:09, Ashutosh Bapat wrote:

I tried to debug the problem somewhat. In set_append_rel_pathlist(),
it finds that at least one child has a parameterized path as the
cheapest path, so it doesn't create an unparameterized path for append
rel. At the same time there is a parameterization common to all the
children, so it doesn't create any path. There seem to be two problems
here
1. The children from second level onwards may not be getting
parameterized for lateral references. That seems unlikely but
possible.

Did you check this? We may be missing on creating index scan paths
with parameterization. If we fix this, we don't need to
re-parameterize Append.

You're right. How about the attached patch that fixes the problem along
these lines? The problem seems to be that multi-level inheritance sets
(partitioned tables) are not handled in create_lateral_join_info(), which
means that lateral_relids and lateral_referencers of the root relation are
not being propagated to the partitions below level 1.

I'm getting concerned about one thing though - for a given *regular*
inheritance set, the root->append_rel_list would be scanned only once; But
for a *partitioned table* inheritance set, it would be scanned for every
partitioned table in the set (ie, the root table and internal partitions).

Thanks,
Amit

Attachments:

multi-level-inh-lateral-join-info.patchtext/x-diff; name=multi-level-inh-lateral-join-info.patchDownload
commit d69aeabcfcb58f349602a1b7392c611045c37465
Author: amit <amitlangote09@gmail.com>
Date:   Tue Sep 27 20:01:44 2016 +0900

    Consider multi-level partitioned tables in create_lateral_join_info().

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..74734f2 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,19 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited from the parent to its children. That's
+		 * because they are not linked directly with the parent via
+		 * AppendRelInfo's (see expand_inherited_rte_internal()).
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
#58Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#46)
9 attachment(s)
Re: Declarative partitioning - another take

Thanks a lot for the review and sorry about the delay in replying.
Combining responses to two emails.

On 2016/09/20 5:06, Robert Haas wrote:

On Mon, Aug 15, 2016 at 7:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

+    if (partexprs)
+        recordDependencyOnSingleRelExpr(&myself,
+                                        (Node *) partexprs,
+                                        RelationGetRelid(rel),
+                                        DEPENDENCY_NORMAL,
+                                        DEPENDENCY_IGNORE);

I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
here. Instead, you could just add an additional Boolean argument to
recordDependencyOnSingleRelExpr. That seems less likely to create
bugs in unrelated portions of the code.

I did consider a Boolean argument instead of a new DependencyType value,
however it felt a bit strange to pass a valid value for the fifth argument
(self_behavior) and then ask using a separate parameter that it (a
self-dependency) is to be ignored. By the way, no pg_depend entry is
created on such a call, so the effect of the new type's usage seems
localized to me. Thoughts?

I think that's not a very plausible argument. If you add a fifth
argument to that function, then only that function needs to know about
the possibility of ignoring self-dependencies. If you add a new
dependency type, then everything that knows about DependencyType needs
to know about them. That's got to be a much larger surface area for
bugs. Also, if you look around a bit, I believe you will find other
examples of cases where one argument is used only for certain values
of some other argument. That's not a novel design pattern.

I removed DEPENDENCY_IGNORE. Does the following look good or am I still
missing something?

@@ -83,7 +78,6 @@ typedef enum DependencyType
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
DEPENDENCY_PIN = 'p',
- DEPENDENCY_IGNORE = 'g'

@@ -194,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress
*depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
                                 Node *expr, Oid relId,
                                 DependencyType behavior,
-                                DependencyType self_behavior);
+                                DependencyType self_behavior,
+                                bool ignore_self);

@@ -1450,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress
*depender,
context.addrs->numrefs = outrefs;

         /* Record the self-dependencies */
-        recordMultipleDependencies(depender,
-                                   self_addrs->refs, self_addrs->numrefs,
-                                   self_behavior);
+        if (!ignore_self)
+            recordMultipleDependencies(depender,
+                                       self_addrs->refs, self_addrs->numrefs,
+                                       self_behavior);
@@ -138,7 +138,7 @@ StorePartitionKey(Relation rel,
                                         (Node *) partexprbin,
                                         RelationGetRelid(rel),
                                         DEPENDENCY_NORMAL,
-                                        DEPENDENCY_IGNORE);
+                                        DEPENDENCY_NORMAL, true);

Re-reviewing 0001.

+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>

This documentation doesn't match pg_partition_table.h, which has
partexprsrc and partexprbin. I don't understand why it's a good idea
to have both, and there seem to be no comments or documentation
supporting that choice anywhere.

Removed partexprsrc, I realized the design whereby it was required to be
stored in the catalog was a dubious one after all.

+      The optional <literal>PARTITION BY</> clause specifies a method of
+      partitioning the table and the corresponding partition key.  Table
+      thus created is called <firstterm>partitioned</firstterm> table.  Key
+      consists of an ordered list of column names and/or expressions when
+      using the <literal>RANGE</> method, whereas only a single column or
+      expression can be specified when using the <literal>LIST</> method.
+      The type of a key column or an expression must have an associated
+      btree operator class or one must be specified along with the column
+      or the expression.

Both of the sentences in this paragraph that do not begin with "the"
need to begin with "the". (In my experience, it's generally a feature
of English as spoken in India that connecting words like "the" and "a"
are sometimes left out where non-Indian speakers of English would
include them, so it would be good to watch out for this issue in
general.)

Thanks for the tip, will try to be careful (rules about a/an/the's can be
too subtle for me sometimes, so any help is much appreciated).

Also, I think this should be rephrased a bit to be more clear about
how the partitioning key works, like this: The optional
<literal>PARTITION BY</literal> clause specifies a method of
partitioning the table. The table thus created is called a
<firstterm>partitioned</firstterm> table. The parenthesized list of
expressions forms the <firsttem>partitioning key</firstterm> for the
table. When using range partitioning, the partioning key can include
multiple columns or expressions, but for list partitioning, the
partitioning key must consist of a single column or expression. If no
btree operator class is specified when creating a partitioned table,
the default btree operator class for the datatype will be used. If
there is none, an error will be reported.

Revised text along these lines.

+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);

Why? None of the reloptions that pertain to heap seem relevant to a
relkind without storage.

But, ah, do partitioned tables have storage? I mean, do we end up
with an empty file, or no relfilenode at all? Can I CLUSTER, VACUUM,
etc. a partitioned table? It would seem cleaner for the parent to
have no relfilenode at all, but I'm guessing we might need some more
changes for that to work out.

Things were that way initially, that is, the parent relations had no
relfilenode. I abandoned that project however. The storage-less parent
thing seemed pretty daunting to me to handle right away. For example,
optimizer and the executor code needed to be taught about the parent rel
appendrel member that used to be included as part of the result of
scanning the inheritance set but was no longer.

That said, there are still diffs in the patch resulting from simply
informing various sites in the code that there is a new relkind
RELKIND_PARTITIONED_TABLE, most of the sites giving it the same treatment
as RELKIND_RELATION. Except maybe that the new relkind serves as a
convenient shorthand for specifying to various modules that the relation
is a partitioned table that is in some regards to be treated differently
from the ordinary tables.

+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\

Whitespace. Also, here and elsewhere, how about using alphabetical
order, or anyway preserving it insofar as the existing list is
alphabetized?

Fixed.

+    /* Remove NO INHERIT flag if rel is a partitioned table */
+    if (is_no_inherit &&
+        rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                 errmsg("cannot add NO INHERIT constraint to
partitioned table \"%s\"",
+                         RelationGetRelationName(rel))));

The code and the comment disagree. I think the code is right and the
comment should be adjusted to say something like /* Partitioned tables
do not have storage, so a NO INHERIT constraint makes no sense. */

Rewrote the comment.

+ * IDENTIFICATION
+ *        src/backend/utils/misc/partition.c

Wrong.

This file is no longer part of the 0001 patch since I moved the file's
only function to relcache.c as mentioned below. Fixed in the later patch
nonetheless.

+} KeyTypeCollInfo;

I don't like this type name very much. Can we get "Part" in there someplace?

It doesn't seem to be very well-designed, either. The number of
entries in each array is determined by the partnatts flag in
PartitionKeyData, which has also got various other arrays whose
lengths are determined by partnatts. Why do we have some arrays in
one structure and some arrays in another structure? Would it hurt
anything to merge everything into one structure? Or could
PartitionKeyData include a field of type KeyTypeCollInfo rather than
KeyTypeCollInfo *, saving one pointer reference every place we access
this data?

I followed your advice to just move the typ* arrays into the
PartitionKeyData struct.

+ /* Allocate in the supposedly short-lived working context */

Why supposedly?

When writing the code, I was thinking of the following header comment that
I copied from somewhere:

* Note that the partition key data attached to a relcache entry must be
* 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.

Removed the comment as it is not very illuminating anymore.

+    datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+                        RelationGetDescr(catalog),
+                        &isnull);

Isn't the point of putting the fixed-length fields first that we can
use GETSTRUCT() here? And even for partattrs as the first
variable-length thing?

Right, fixed.

+        /*
+         * Run the expressions through eval_const_expressions. This is
+         * not just an optimization, but is necessary, because eventually
+         * the planner will be comparing them to similarly-processed qual
+         * clauses, and may fail to detect valid matches without this.
+         * We don't bother with canonicalize_qual, however.
+         */

I'm a bit confused by this, because I would think this processing
ought to have been done before storing anything in the system
catalogs. I don't see why it should be necessary to do it again after
pulling data back out of the system catalogs.

The pattern matches what's done for other expressions that optimizer deals
with, such as CHECK, index key, and index predicate expressions.

+            Value *str = lfirst(partexprsrc_item);
+            key->partcolnames[i] = pstrdup(str->val.str);

Should have a blank line in between.

Fixed.

+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+    return key->strategy;
+}
+
[ ... ]
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+    return key->tcinfo->typmod[col];
+}

If we're going to add notation for this, I think we should use macros
(or static inline functions defined in the header file). Doing it
this way adds more cycles for no benefit.

OK, done using the static inline functions defined in the header file way.

+    newkey->partattrs = (AttrNumber *)
+                            palloc0(newkey->partnatts * sizeof(AttrNumber));
+    memcpy(newkey->partattrs, fromkey->partattrs,
+                            newkey->partnatts * sizeof(AttrNumber));

It's wasteful to use palloc0 if you're immediately going to overwrite
every byte in the array. Use regular palloc instead.

Done.

+    /* Only this can ever be NULL */
+    if (!partexprbinDatum)
+    {
+        nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+        nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+    }

How can it be valid to have no partitioning expressions?

Keys that are simply column names are resolved to attnums and stored
likewise. If some key is an expression, then corresponding attnum is 0
and the expression itself is added to the list that gets stored into
partexprbin. It is doing the same thing as index expressions.

+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);

Is this really needed? Isn't the caller going to do something similar
pretty soon?

Oops, I removed that in one of the later patches but shouldn't be there in
the first place. Fixed.

+ heap_freetuple(tuple);

Probably useless - might as well let the context reset clean it up.

Removed.

+    simple_heap_delete(rel, &tuple->t_self);
+
+    /* Update the indexes on pg_partitioned_table */
+    CatalogUpdateIndexes(rel, tuple);

You don't need CatalogUpdateIndexes() after a delete, only after an
insert or update.

Ah right, removed.

+    if (classform->relkind != relkind &&
+                (relkind == RELKIND_RELATION &&
+                    classform->relkind != RELKIND_PARTITIONED_TABLE))

That's broken. Note that all of the conditions are joined using &&,
so if any one of them fails then we won't throw an error. In
particular, it's no longer possible to throw an error when relkind is
not RELKIND_RELATION.

You are right. I guess it would have to be the following:

+    if ((classform->relkind != relkind &&
+         classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+        (classform->relkind == RELKIND_PARTITIONED_TABLE &&
+         relkind != RELKIND_RELATION))

Such hackishness could not be helped because we have a separate DROP
command for every distinct relkind, except we overload DROP TABLE for both
regular and partitioned tables.

+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+    if (node == NULL)
+        return false;
+
+    if (IsA(node, Var))
+    {
+        Var       *var = (Var *) node;
+        char   *varattname = get_attname(context->relid, var->varattno);
+
+        if (!strcmp(varattname, context->attname))
+            return true;
+    }
+
+    return expression_tree_walker(node, find_attr_reference_walker, context);
+}

Hrm. The comment says we're matching on attnum, but the code says
we're matching on attname. is_partition_attr() has the same confusion
between comments and code. Maybe instead of this whole approach it
would be better to use pull_varattnos(), then get_attnum() to find the
attribute number for the one you want, then bms_is_member().

I like the idea to use pull_varattnos(), so done that way.

+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+    PartitionBy       *partby;
+    ParseState       *pstate;
+    RangeTblEntry  *rte;
+    ListCell       *l;
+
+    partby = (PartitionBy *) makeNode(PartitionBy);
+
+    partby->strategy = partitionby->strategy;
+    partby->location = partitionby->location;
+    partby->partParams = NIL;
+
+    /*
+     * Create a dummy ParseState and insert the target relation as its sole
+     * rangetable entry.  We need a ParseState for transformExpr.
+     */
+    pstate = make_parsestate(NULL);

Why isn't this logic being invoked from transformCreateStmt()? Then
we could use the actual parseState for the query instead of a fake
one.

Because we need an open relation for it to work, which in this case there
won't be until after we have performed heap_create_with_catalog() in
DefineRelation(). Mainly because we need to perform transformExpr() on
expressions. That's similar to how cookConstraint() on the new CHECK
constraints cannot be performed earlier. Am I missing something?

+            if (IsA(expr, CollateExpr))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                         errmsg("cannot use COLLATE in partition key
expression")));

I assume there is a good reason for this seemingly-arbitrary
restriction, but there's no comment saying what it is. One thing
that's odd is that this will only prohibit a CollateExpr at the top
level, not in some more-deeply nested position. That seems
suspicious.

Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
just remove the above code. Of course, we must then record the collation
in the catalog alongside other user-specified information such as operator
class. Currently, if the key is a simple column we use its attcollation
and if it's an expression then we use its exprCollation().

When I first wrote it, I wasn't sure what the implications of explicit
collations would be for partitioning. There are two places where it comes
into play: a) when comparing partition key values using the btree compare
function b) embedded as varcollid and inputcollid in implicitly generated
check constraints for partitions. In case of the latter, any mismatch
with query-specified collation causes constraint exclusion proof to be
canceled. When it's default collations everywhere, the chances of that
sort of thing happening are less. If we support user-specified collations
on keys, then things will get a little bit more involved.

Thoughts?

+                /*
+                 * User wrote "(column)" or "(column COLLATE something)".
+                 * Treat it like simple attribute anyway.
+                 */

Evidently, the user did not do that, because you just prohibited the
second one of those.

Holds true now that I have removed the prohibition.

+                if (IsA(expr, Const))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("cannot use a constant expression
as partition key")));
+
+                if (contain_mutable_functions(expr))
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("functions in partition key
expression must be marked IMMUTABLE")));

Do these checks parallel what we do for CHECK constraints? It might
be good to apply about the same level of rigor in both cases.

Both of these checks are not done for CHECK constraints. The only check
performed on them in cookConstraint() checks whether the expression is of
boolean type.

However in the present case, this is just one side of a whole partition
constraint (the other piece being individual partition's bound value), so
should be treated a bit differently from the CHECK constraints. I modeled
this on ComputeIndexAttrs() checks viz. the following:

/*
* An expression using mutable functions is probably wrong,
* since if you aren't going to get the same result for the
* same data every time, it's not clear what the index entries
* mean at all.
*/
if (CheckMutability((Expr *) expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("functions in index expression must be marked IMMUTABLE")));

Likewise if a partition key expression contained mutable functions, same
input row could be mapped to different partitions based on the result of
expression computed using the input values. So, it seems prudent to
enforce immutability unlike CHECK constraints. Am I missing something?

+                exprsrc = deparse_expression(expr,
+                            deparse_context_for(RelationGetRelationName(rel),
+                                                RelationGetRelid(rel)),
+                                       false, false);

Why bother? The output of this doesn't seem like a useful thing to
store. The fact that we've done similar things elsewhere doesn't make
it a good idea. I think we did it in other cases because we used to
be dumber than we are now.

Deparsed expressions are no longer stored in the catalog.

+                        (errcode(ERRCODE_UNDEFINED_OBJECT),
+                         errmsg("data type %s has no default btree
operator class",
+                                format_type_be(atttype)),
+                         errhint("You must specify an existing btree
operator class or define one for the type.")));

The hint is not really accurate, because the type may well have a
btree operator class. Just not a default one.

Changed to: "You must specify a btree operator class or define a default
btree operator class for the data type."

+                            char    relkind = ((CreateStmt *)
stmt)->partby != NULL
+                                                    ? RELKIND_PARTITIONED_TABLE
+                                                    : RELKIND_RELATION;

Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
RELKIND_PARTITION_TABLE; }

Done. By the way, I made the ereport say the following: "unexpected
relkind value passed to DefineRelation", did you intend it to say
something else?

+ RelationBuildPartitionKey(relation);

I wonder if RelationBuildPartitionKey should really be in relcache.c.
What do we do in similar cases?

There a number of RelationBuild* functions that RelationBuildDesc calls
that are reside outside relcache.c in respective modules - trigger.c
(BuildTrigger), policy.c (BuildRowSecurity), whereas some others such as
RelationInitIndexAccessInfo() are housed in relcache.c.

BuildPartitionKey used to be in relcache.c and can be moved back there. So
did.

+} PartitionBy;

Maybe PartitionSpec?

A later patch uses PartitionListSpec and PartitionRangeSpec as parse nodes
for partition bounds. How about call PartitionBy PartitionKeySpec and the
bound nodes just mentioned PartitionBoundList and PartitionBoundRange,
respectively?

Attached revised patches. In addition to addressing comments in this
email (which addressed only the patch 0001), number of other fixes [1]/messages/by-id/2896956a-8af0-3f91-a3bc-1e5225ae7ed2@lab.ntt.co.jp and
other miscellaneous improvements [2]/messages/by-id/98d6c7e7-4919-4970-a158-49f740101812@lab.ntt.co.jp have been included.

Thanks,
Amit

[1]: /messages/by-id/2896956a-8af0-3f91-a3bc-1e5225ae7ed2@lab.ntt.co.jp
/messages/by-id/2896956a-8af0-3f91-a3bc-1e5225ae7ed2@lab.ntt.co.jp

[2]: /messages/by-id/98d6c7e7-4919-4970-a158-49f740101812@lab.ntt.co.jp
/messages/by-id/98d6c7e7-4919-4970-a158-49f740101812@lab.ntt.co.jp

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-6.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-6.patchDownload
From 891c823431ccfeac4e32bf12be20f1d403f3a5e3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.

2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                    |  102 +++++++-
 doc/src/sgml/ref/create_table.sgml            |   56 ++++
 src/backend/access/common/reloptions.c        |    2 +
 src/backend/catalog/Makefile                  |    4 +-
 src/backend/catalog/aclchk.c                  |    2 +
 src/backend/catalog/dependency.c              |   10 +-
 src/backend/catalog/heap.c                    |   30 ++-
 src/backend/catalog/index.c                   |    4 +-
 src/backend/catalog/objectaddress.c           |    5 +-
 src/backend/catalog/pg_constraint.c           |    2 +-
 src/backend/catalog/pg_partitioned_table.c    |  153 ++++++++++
 src/backend/commands/analyze.c                |    2 +
 src/backend/commands/copy.c                   |    6 +
 src/backend/commands/indexcmds.c              |    7 +-
 src/backend/commands/lockcmds.c               |    2 +-
 src/backend/commands/policy.c                 |    2 +-
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/sequence.c               |    1 +
 src/backend/commands/tablecmds.c              |  366 ++++++++++++++++++++++++-
 src/backend/commands/trigger.c                |    7 +-
 src/backend/commands/vacuum.c                 |    1 +
 src/backend/executor/execMain.c               |    2 +
 src/backend/executor/nodeModifyTable.c        |    1 +
 src/backend/nodes/copyfuncs.c                 |   33 +++
 src/backend/nodes/equalfuncs.c                |   28 ++
 src/backend/nodes/outfuncs.c                  |   26 ++
 src/backend/parser/gram.y                     |  110 ++++++--
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_expr.c               |    5 +
 src/backend/parser/parse_func.c               |    3 +
 src/backend/parser/parse_utilcmd.c            |   69 +++++
 src/backend/rewrite/rewriteDefine.c           |    1 +
 src/backend/rewrite/rewriteHandler.c          |    1 +
 src/backend/utils/cache/relcache.c            |  230 +++++++++++++++-
 src/backend/utils/cache/syscache.c            |   12 +
 src/include/catalog/dependency.h              |    5 +-
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_partitioned_table.h    |   67 +++++
 src/include/catalog/pg_partitioned_table_fn.h |   28 ++
 src/include/commands/defrem.h                 |    2 +
 src/include/nodes/nodes.h                     |    2 +
 src/include/nodes/parsenodes.h                |   35 +++
 src/include/parser/kwlist.h                   |    1 +
 src/include/parser/parse_node.h               |    3 +-
 src/include/pg_config_manual.h                |    5 +
 src/include/utils/rel.h                       |   75 +++++
 src/include/utils/syscache.h                  |    1 +
 src/test/regress/expected/alter_table.out     |   46 +++
 src/test/regress/expected/create_table.out    |  154 +++++++++++
 src/test/regress/expected/sanity_check.out    |    1 +
 src/test/regress/sql/alter_table.sql          |   34 +++
 src/test/regress/sql/create_table.sql         |  132 +++++++++
 53 files changed, 1841 insertions(+), 51 deletions(-)
 create mode 100644 src/backend/catalog/pg_partitioned_table.c
 create mode 100644 src/include/catalog/pg_partitioned_table.h
 create mode 100644 src/include/catalog/pg_partitioned_table_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..9cc7eed 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy (or method); <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprbin</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..b6f1f6e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,40 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firsttem>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns or expressions in the partition key and partition
+      bound constraints associated with the individual partitions.
+     </para>
+
+     <para>
+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1405,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..08b43ad 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_table.o pg_type.o storage.o toasting.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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..d54b20a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2043,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..907101f
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,153 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ *	  Routines to support manipulation of the pg_partitioned_table 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_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprbin,
+				  Oid *partopclass)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	Datum		partexprbinDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+	/* Cannot already exist */
+	Assert(!HeapTupleIsValid(tuple));
+
+	/* Copy the partition key, opclass info into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprbin)
+	{
+		char       *exprbinString;
+
+		exprbinString = nodeToString(partexprbin);
+		partexprbinDatum = CStringGetTextDatum(exprbinString);
+		pfree(exprbinString);
+	}
+	else
+		partexprbinDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprbinDatum)
+		nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Make this relation dependent on a few things: */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Store dependencies on anything mentioned in the key expressions.
+	 * However, ignore the column references which causes self-dependencies
+	 * to be created that are undesirable.  That is done by asking the
+	 * dependency-tracking sub-system to ignore any such dependencies.
+	 */
+	if (partexprbin)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprbin,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_NORMAL, true);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION  && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..bdeafdc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -39,6 +39,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -65,6 +66,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +218,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +441,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, Oid *partopclass);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +504,15 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("unexpected relkind value passed to DefineRelation")));
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +617,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -697,6 +719,23 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition key, if any */
+	if (stmt->partspec)
+	{
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		List		   *partexprbin = NIL;
+
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprbin, partopclass);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, stmt->partspec->strategy, partnatts,
+						  partattrs, partexprbin, partopclass);
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -955,7 +994,17 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if ((classform->relkind != relkind &&
+		 classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+		(classform->relkind == RELKIND_PARTITIONED_TABLE &&
+		 relkind != RELKIND_RELATION))
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1342,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1571,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2219,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4349,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4586,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5477,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5753,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexpr_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_key_natts(key);
+	partexprs = get_partition_key_exprs(key);
+
+	partexpr_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if(partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (attnum == partattno)
+				return true;
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexpr_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			if (is_expr)
+				*is_expr = true;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexpr_item = lnext(partexpr_item);
+
+			index = -1;
+			while ((index = bms_next_member(expr_attrs, index)) > 0)
+			{
+				AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+				if (attno == attnum)
+					return true;
+			}
+			partexpr_item = lnext(partexpr_item);
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5829,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5874,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6405,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8006,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7892,6 +8037,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7907,7 +8065,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8934,6 +9093,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9556,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9979,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10179,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10068,6 +10235,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11446,6 +11620,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12070,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12224,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12236,181 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_KEY);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprbin, Oid *partopclass)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		opclassOid;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprbin = lappend(*partexprbin, expr);
+
+				/*
+				 * An expression using mutable functions is probably wrong even
+				 * even to use in a partition key
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for an partition key expression.
+				 */
+			}
+		}
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(opclassOid))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			opclassOid = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		partopclass[attn++] = opclassOid;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..a295162 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5115,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..fa5037b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3409,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 ae86954..f0b63f1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3851,6 +3872,11 @@ outNode(StringInfo str, const void *obj)
 				break;
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
 				break;
 
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a2e99a6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	KEY
 
 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
-	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
 	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY RANGE '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = PARTITION_STRAT_RANGE;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| PARTITION BY LIST '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = PARTITION_STRAT_LIST;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->opclass = $2;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->opclass = $4;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
 			| LAST_P
 			| LEAKPROOF
 			| LEVEL
+			| LIST
 			| LISTEN
 			| LOAD
 			| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..7f496ea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_KEY:
+			return "partition key expression";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..d1de990 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_KEY:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..c32edb7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot use more than %d columns in partition key",
+						PARTITION_MAX_KEYS)));
+
+		if (stmt->partspec->strategy == PARTITION_STRAT_LIST &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot use more than one column in partition key"),
+				 errdetail("Only one column allowed with list partitioning.")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..57c2933 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,214 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * 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 void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	ListCell	   *partexprbin_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catlog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprbin,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through eval_const_expressions. This is
+		 * not just an optimization, but is necessary, because eventually
+		 * the planner will be comparing them to similarly-processed qual
+		 * clauses, and may fail to detect valid matches without this.
+		 * We don't bother with canonicalize_qual, however.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprbin_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprbin_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprbin_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprbin_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1263,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2264,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3207,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5261,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
 } DependencyType;
 
 /*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..37c705f
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	pg_node_tree	partexprbin;	/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				6
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partexprbin	6
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..fb003ef
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ *	  prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprbin,
+					Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif   /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..373b8ed 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,40 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy to use (one of the below defined)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST	'l'
+#define PARTITION_STRAT_RANGE	'r'
+
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char		strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1787,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
 PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
 PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
 PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
 PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..99f68c7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_KEY		/* partition key expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..c775ca4 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,30 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	List	   *partexprs;		/* partition key expressions, if any */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Type and collation information of partition attributes */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +118,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +559,54 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_key_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_key_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_key_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..1394362 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,157 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot use more than one column in partition key
+DETAIL:  Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..d383f58 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,135 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-6.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-6.patchDownload
From 971432b577d1caa75566926f63d01d68ed4d1f8f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-6.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-6.patchDownload
From ed124df5caf21d97c785b1d3d461783bac6eda6b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |   33 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1772 ++++++++++++++++++++++++++++
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/lockcmds.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  774 ++++++++++--
 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                  |  208 ++++-
 src/backend/parser/parse_agg.c             |    1 -
 src/backend/parser/parse_utilcmd.c         |  366 ++++++-
 src/backend/utils/cache/relcache.c         |  105 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   59 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   42 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/parser/parse_node.h            |    2 +-
 src/include/utils/rel.h                    |    9 +
 src/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  188 +++
 src/test/regress/sql/alter_table.sql       |  192 +++
 src/test/regress/sql/create_table.sql      |  139 +++
 34 files changed, 4399 insertions(+), 163 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9cc7eed..fe701f6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1233,6 +1315,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..007782c 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 b6f1f6e..45dc268 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>
@@ -1423,7 +1493,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 08b43ad..291aa64 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d54b20a..80f64a6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2052,6 +2064,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2478,7 +2497,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..6af629c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1772 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundList *list_spec,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundRange *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundList  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionBoundList));
+					list_spec = (PartitionBoundList *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->parttypbyval[0],
+													  key->parttyplen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundRange  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionBoundRange));
+					range_spec = (PartitionBoundRange *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				ListInfo   *listinfo;
+				int		   *mapping;
+				int			next_index = 0;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+				mapping = (int *) palloc(sizeof(int) * nparts);
+
+				/* Initialize with invalid mapping values */
+				for (i = 0; i < nparts; i++)
+					mapping[i] = -1;
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they are same for any two partitioning schemes
+				 * with the same lists. One way to achieve this is to assign
+				 * indexes in the same order as the least values of the
+				 * individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo->null_index = mapping[null_partition_index];
+				else
+					listinfo->null_index = -1;
+
+				/*
+				 * Now assign OIDs from the original array into mapped indexes
+				 * of the result array.  Order of OIDs in the former is defined
+				 * by the catalog scan that retrived them, whereas that in the
+				 * latter is defined by canonicalized representation of list
+				 * values (viz. ordering of list values).
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[mapping[i]] = oids[i];
+
+				result->bounds->listinfo = listinfo;
+				pfree(mapping);
+				break;
+			}
+
+			case PARTITION_STRAT_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			PartitionBoundList *list;
+
+			Assert(IsA(bound, PartitionBoundList));
+			list = (PartitionBoundList *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, list->location)));
+				}
+			}
+			break;
+		}
+
+		case PARTITION_STRAT_RANGE:
+		{
+			PartitionBoundRange *range;
+
+			Assert(IsA(bound, PartitionBoundRange));
+			range = (PartitionBoundRange *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+	Oid		parentOID = InvalidOid;
+	Relation rel;
+
+	rel = heap_open(relid, AccessShareLock);
+	if (rel->rd_rel->relispartition)
+		parentOID = get_partition_parent(relid);
+	heap_close(rel, AccessShareLock);
+
+	if (!OidIsValid(parentOID))
+		return NIL;
+
+	return list_concat(list_make1_oid(parentOID),
+					   get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionBoundList))
+	{
+		PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionBoundRange))
+	{
+		PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionBoundList *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionBoundRange *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel)
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0], -1,
+							   get_typcollation(key->partopcintype[0]),
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->parttypcoll[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *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 *nulltest;
+
+		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->parttypid[0],
+									   key->parttypmod[0],
+									   key->parttypcoll[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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  make_opclause(operoid, BOOLOID,
+										false,
+										(Expr *) key_col,
+										(Expr *) key_val,
+										InvalidOid,
+										key->parttypcoll[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	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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->parttypcoll[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
+			 */
+			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->parttypcoll[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/* Build leftop ge/gt lower_val */
+			strategy = 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->parttypcoll[i]));
+
+			/* Build leftop le/lt upper_val */
+			strategy = i < 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->parttypcoll[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->parttypid[col],
+								  key->parttypid[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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										  key->parttypbyval[0],
+										  key->parttyplen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->parttypcoll[0],
+										   val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+										  key->parttyplen[i]);
+			i++;
+		}
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+									   key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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->parttypcoll[i],
+												 val1[i], val2[i]));
+		if (result != 0)
+			break;
+	}
+
+	return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 bdeafdc..e3cdf2f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -164,6 +165,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 */
+	List	   *partition_quals; /* partition check quals 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 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -445,6 +449,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprbin, Oid *partopclass);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -483,6 +492,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -596,10 +606,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
@@ -674,6 +690,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -697,6 +731,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -994,6 +1029,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
 	 * but RemoveRelations() can only pass one relkind for a given relation.
@@ -1082,7 +1124,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1460,7 +1503,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;
@@ -1505,8 +1549,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),
@@ -1533,6 +1577,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),
@@ -1568,18 +1624,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -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 (classform->relispartition && !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
@@ -2441,7 +2530,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2589,11 +2678,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2835,8 +2925,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3115,6 +3208,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 +3530,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);
@@ -3502,7 +3606,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3751,6 +3861,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 +4052,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4016,6 +4132,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4080,6 +4197,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4269,6 +4395,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4466,7 +4598,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4788,6 +4921,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5310,6 +5448,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5835,6 +5993,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5927,9 +6090,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6318,8 +6483,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7881,7 +8048,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);
@@ -7940,8 +8109,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8013,6 +8184,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10180,6 +10356,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10192,12 +10373,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;
 
@@ -10242,37 +10418,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10307,6 +10457,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);
 
@@ -10321,16 +10534,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;
 }
 
 /*
@@ -10381,7 +10586,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
@@ -10399,12 +10604,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10426,14 +10635,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10444,8 +10657,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10460,7 +10675,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10474,7 +10691,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.
@@ -10493,10 +10710,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10543,7 +10764,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10551,7 +10774,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10562,6 +10787,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10575,7 +10801,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10586,6 +10814,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10599,13 +10867,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];
@@ -10614,19 +10880,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10636,7 +10893,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);
 
@@ -10657,11 +10914,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
@@ -10670,7 +10936,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)))
@@ -10732,7 +10998,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);
 
@@ -10763,7 +11029,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)
@@ -10775,30 +11041,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;
 }
 
 /*
@@ -12414,3 +12670,271 @@ ComputePartitionAttrs(Relation rel, 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,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a295162..61afe69 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4201,6 +4203,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+	PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+	PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+	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
  * ****************************************************************
@@ -5121,6 +5160,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundList:
+			retval = _copyPartitionBoundList(from);
+			break;
+		case T_PartitionBoundRange:
+			retval = _copyPartitionBoundRange(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 fa5037b..69aed5a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,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);
@@ -2656,6 +2658,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *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
  */
@@ -3415,6 +3448,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundList:
+			retval = _equalPartitionBoundList(a, b);
+			break;
+		case T_PartitionBoundRange:
+			retval = _equalPartitionBoundRange(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 f0b63f1..55628f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -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);
@@ -3289,6 +3291,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *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'
@@ -3878,6 +3899,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundList:
+				_outPartitionBoundList(str, obj);
+				break;
+			case T_PartitionBoundRange:
+				_outPartitionBoundRange(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+	READ_LOCALS(PartitionBoundList);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+	READ_LOCALS(PartitionBoundRange);
+
+	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
@@ -2497,6 +2526,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionBoundList();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionBoundRange();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a2e99a6..0e22d64 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -546,6 +546,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partspec>	PartitionSpec OptPartitionSpec
 %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.
@@ -571,7 +579,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
@@ -587,7 +595,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
@@ -601,7 +610,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 +2382,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 +2509,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionBoundList *n = makeNode(PartitionBoundList);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+					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 +2980,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2930,6 +3063,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2952,6 +3090,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2963,6 +3112,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3126,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 +3148,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 +4700,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11149,6 +11347,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;
@@ -13748,6 +13947,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13794,6 +13994,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13836,6 +14037,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..086de94 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c32edb7..c47e711 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +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 */
@@ -130,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);
 
 
 /*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partspec != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,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->partspec)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -360,6 +373,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);
@@ -899,6 +913,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;
@@ -1118,6 +1133,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;
@@ -2581,6 +2597,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2663,6 +2680,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;
@@ -3027,3 +3060,334 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_key_strategy(key);
+	int				partnatts = get_partition_key_natts(key);
+	List		   *partexprs = get_partition_key_exprs(key);
+	PartitionBoundList  *list, *result_list;
+	PartitionBoundRange *range, *result_range;
+
+	switch (strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (!IsA(bound, PartitionBoundList))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionBoundList *) bound;
+			result_list = makeNode(PartitionBoundList);
+
+			foreach(cell, list->values)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\""
+							" of key column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+					 parser_errposition(cxt->pstate, exprLocation(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:
+		{
+			char	*colname;
+
+			if (!IsA(bound, PartitionBoundRange))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionBoundRange *) bound;
+
+			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(PartitionBoundRange);
+			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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(cxt->pstate, exprLocation(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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								colname),
+							 parser_errposition(cxt->pstate, exprLocation(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 57c2933..50e5fed 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1136,6 +1139,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1263,13 +1319,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2266,6 +2327,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2414,11 +2479,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2429,6 +2495,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);
@@ -2459,6 +2526,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
@@ -2514,6 +2584,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 */
@@ -3740,6 +3817,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5263,6 +5354,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..ea7806e
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundList,
+	T_PartitionBoundRange,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 373b8ed..ad331dc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -734,6 +735,40 @@ typedef struct PartitionSpec
 	int			location;	/* token location, or -1 if unknown */
 } PartitionSpec;
 
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1561,7 +1596,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
@@ -1787,7 +1824,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 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 99f68c7..0818d9c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,7 @@ 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 */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c775ca4..cc51bc9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -120,6 +120,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -607,6 +610,12 @@ get_partition_col_typmod(PartitionKey key, int col)
 	return key->parttypmod[col];
 }
 
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 260ec34..a6484af 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -433,3 +433,191 @@ Table "public.describe_list_key"
 Partition key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 7a1f8de..008ffd4 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -414,3 +414,142 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a 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

0004-psql-and-pg_dump-support-for-partitions-6.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-6.patchDownload
From 5d38432aef6e0b103fa97a43b2ef1d6fb67782eb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++++----
 src/test/regress/expected/create_table.out |   39 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 ++++
 7 files changed, 393 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..8b84df4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundList:
+			{
+				PartitionBoundList *list_spec = (PartitionBoundList *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionBoundRange:
+			{
+				PartitionBoundRange *range_spec = (PartitionBoundRange *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 854dddc..b6b8832 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6141,6 +6141,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15318,6 +15375,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15346,8 +15414,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15376,7 +15447,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15441,15 +15513,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15619,6 +15698,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index db6dc5c..9834599 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index a6484af..90fb006 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,45 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 008ffd4..8e1ffd5 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-6.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-6.patchDownload
From 2eea296413455fb19ec5ce21d66eb84ef0fabb32 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.

Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
 src/backend/optimizer/plan/initsplan.c |   17 ++-
 src/backend/optimizer/prep/prepunion.c |  282 +++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 3 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Do not flatten the inheritance hierarchy if partitioned table, unless
+	 * this is the result relation.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rti != root->parse->resultRelation)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
-		 */
-		if (childOID != parentOID)
-		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
-		}
-
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (oldrc)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-6.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-6.patchDownload
From 606a2236f804b9b34a2f8dbe70dba7ca73fc3b7a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-6.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-6.patchDownload
From 670e4866f959549def0cbee0619b73c3818b2181 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6af629c..2d1602b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionDesc() */
 static int32 list_value_cmp(const void *a, const void *b, void *arg);
 static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -774,6 +833,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1423,6 +1529,106 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index ea7806e..1ecd5d6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -56,4 +57,8 @@ extern List *get_partition_ancestors(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-6.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-6.patchDownload
From 6fcded2ed769ef7dc34a514d211a865309f1d7e8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  343 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   60 ++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    9 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 909 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2d1602b..5904bb6 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * RelationBuildPartitionDesc
@@ -1545,7 +1560,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1629,6 +1644,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRAT_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -1976,3 +2252,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 157d219..932ed62 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1690,6 +1786,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2260,7 +2374,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2416,10 +2533,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2471,6 +2624,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2491,7 +2669,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2521,7 +2708,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2554,7 +2748,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e3cdf2f..0a3a367 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1245,6 +1245,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..32f4031 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6152,6 +6153,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..ced820f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "catalog/pg_partitioned_table_fn.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -798,8 +799,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1ecd5d6..b6607d8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -61,4 +63,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 697c90f..6e51773 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1141,6 +1142,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-6.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-6.patchDownload
From 82577d46c647b48175a933a035deb004ae84fba3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f43352c..8084029 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#59Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#58)
Re: Declarative partitioning - another take

On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I removed DEPENDENCY_IGNORE. Does the following look good or am I still
missing something?

You missed your commit message, but otherwise looks fine.

Also, I think this should be rephrased a bit to be more clear about
how the partitioning key works, like this: The optional
<literal>PARTITION BY</literal> clause specifies a method of
partitioning the table. The table thus created is called a
<firstterm>partitioned</firstterm> table. The parenthesized list of
expressions forms the <firsttem>partitioning key</firstterm> for the
table. When using range partitioning, the partioning key can include
multiple columns or expressions, but for list partitioning, the
partitioning key must consist of a single column or expression. If no
btree operator class is specified when creating a partitioned table,
the default btree operator class for the datatype will be used. If
there is none, an error will be reported.

Revised text along these lines.

It seems you copied my typo: my text has "partioning" for
"partitioning" and your patch now has that, too.

Things were that way initially, that is, the parent relations had no
relfilenode. I abandoned that project however. The storage-less parent
thing seemed pretty daunting to me to handle right away. For example,
optimizer and the executor code needed to be taught about the parent rel
appendrel member that used to be included as part of the result of
scanning the inheritance set but was no longer.

Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.

+        /*
+         * Run the expressions through eval_const_expressions. This is
+         * not just an optimization, but is necessary, because eventually
+         * the planner will be comparing them to similarly-processed qual
+         * clauses, and may fail to detect valid matches without this.
+         * We don't bother with canonicalize_qual, however.
+         */

I'm a bit confused by this, because I would think this processing
ought to have been done before storing anything in the system
catalogs. I don't see why it should be necessary to do it again after
pulling data back out of the system catalogs.

The pattern matches what's done for other expressions that optimizer deals
with, such as CHECK, index key, and index predicate expressions.

That's kind of a non-answer answer, but OK. Still, I think you
shouldn't just copy somebody else's comment blindly into a new place.
Reference the original comment, or write your own.

How can it be valid to have no partitioning expressions?

Keys that are simply column names are resolved to attnums and stored
likewise. If some key is an expression, then corresponding attnum is 0
and the expression itself is added to the list that gets stored into
partexprbin. It is doing the same thing as index expressions.

Oh, right. Oops.

+    if (classform->relkind != relkind &&
+                (relkind == RELKIND_RELATION &&
+                    classform->relkind != RELKIND_PARTITIONED_TABLE))

That's broken. Note that all of the conditions are joined using &&,
so if any one of them fails then we won't throw an error. In
particular, it's no longer possible to throw an error when relkind is
not RELKIND_RELATION.

You are right. I guess it would have to be the following:

+    if ((classform->relkind != relkind &&
+         classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+        (classform->relkind == RELKIND_PARTITIONED_TABLE &&
+         relkind != RELKIND_RELATION))

Such hackishness could not be helped because we have a separate DROP
command for every distinct relkind, except we overload DROP TABLE for both
regular and partitioned tables.

Maybe this would be better:

if (relkind == RELKIND_PARTITIONED_TABLE)
syntax_relkind = RELKIND_RELATION;
else
syntax_relkind = rekind;
if (classform->relkind != syntax_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);

Why isn't this logic being invoked from transformCreateStmt()? Then
we could use the actual parseState for the query instead of a fake
one.

Because we need an open relation for it to work, which in this case there
won't be until after we have performed heap_create_with_catalog() in
DefineRelation(). Mainly because we need to perform transformExpr() on
expressions. That's similar to how cookConstraint() on the new CHECK
constraints cannot be performed earlier. Am I missing something?

Hmm, yeah, I guess that's the same thing. I guess I got on this line
of thinking because the function name started with "transform" which
is usually something happening during parse analysis only. Maybe add
a code comment explaining why the work can't be done sooner, just like
what you've written here.

Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
just remove the above code. Of course, we must then record the collation
in the catalog alongside other user-specified information such as operator
class. Currently, if the key is a simple column we use its attcollation
and if it's an expression then we use its exprCollation().

When I first wrote it, I wasn't sure what the implications of explicit
collations would be for partitioning. There are two places where it comes
into play: a) when comparing partition key values using the btree compare
function b) embedded as varcollid and inputcollid in implicitly generated
check constraints for partitions. In case of the latter, any mismatch
with query-specified collation causes constraint exclusion proof to be
canceled. When it's default collations everywhere, the chances of that
sort of thing happening are less. If we support user-specified collations
on keys, then things will get a little bit more involved.

I think it's worth rewinding to general principles here: the only time
a collation has real meaning is in the context of a comparison
operation. If we ask whether A < B, the answer may depend on the
collation that is used to perform the comparison. Any other place
that mentions a collation is only trying to influence the collation
that gets used for some comparison that will happen later - so, for
example, for a table column, attcollation is setting the default
collation for comparisons involving that column. The column is not
itself collated; that doesn't really make sense.

In the case of partitioning, there is really exactly one thing that
matters: everybody's got to agree on the collation to be used to
compare actual or hypothetical values of the partitioning columns
against the partition bounds. If we've got a set of partition bounds
b_1, b_2, ..., b_n and a set of partitions p_1, p_2, ..., p_{n-1} such
that a row in p_i must have a key k such that b_i < k and k < b_{i+1},
and if the meaning of that "<" operator is collation-dependent, then
we had better agree on which collation is in use every single time we
do the test.

Indexes, of course, have this exact same problem, at least if, like
btree, they rely on ordering. We search the index by applying the "<"
operator to compare various values taken from the index tuples with
the value we're trying to find, and it is absolutely vital that the
collation used for lookups remain constant and that it is the same as
the collation used for inserts, which had also better remain constant.
That is why pg_index has an indcollation column of type oidvector: it
tells us which collation to use for each index column. It pairs with
indclass, which tells us which operator class to use for each index
column. I think that partitioning will need exact analogues -
partclass and partcollation - because it has exactly the same problem.
Currently, you've got partclass but not partcollation.

I'd in general recommend that you try to follow the index precedent
here as closely as practical.

However in the present case, this is just one side of a whole partition
constraint (the other piece being individual partition's bound value), so
should be treated a bit differently from the CHECK constraints. I modeled
this on ComputeIndexAttrs() checks viz. the following:

/*
* An expression using mutable functions is probably wrong,
* since if you aren't going to get the same result for the
* same data every time, it's not clear what the index entries
* mean at all.
*/
if (CheckMutability((Expr *) expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("functions in index expression must be marked IMMUTABLE")));

Likewise if a partition key expression contained mutable functions, same
input row could be mapped to different partitions based on the result of
expression computed using the input values. So, it seems prudent to
enforce immutability unlike CHECK constraints. Am I missing something?

OK, seems reasonable.

+                            char    relkind = ((CreateStmt *)
stmt)->partby != NULL
+                                                    ? RELKIND_PARTITIONED_TABLE
+                                                    : RELKIND_RELATION;

Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
RELKIND_PARTITION_TABLE; }

Done. By the way, I made the ereport say the following: "unexpected
relkind value passed to DefineRelation", did you intend it to say
something else?

You don't need it to be translatable because it should only happen if
there's a bug in the code; users shouldn't actually be able to hit it.
So use elog() rather than ereport(). You don't need to include the
function name, either; the user can use \errverbose or \set VERBOSITY
verbose to get the file and line number if they need it. So just
elog(ERROR, "unexpected relkind") should be fine; or maybe elog(ERROR,
"unexpected relkind: %d", (int) relkind), following similar precedent
elsewhere.

Reading through the latest 0001:

+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.

Maybe "stores information about how tables are partitioned".

+ Partitioning strategy (or method); <literal>l</> = list
partitioned table,

I don't think we need "(or method)".

+ <entry><structfield>partexprbin</structfield></entry>

Is there any good reason not to do partexprbin -> partexpr throughout the patch?

+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.

I would delete "in turn" and the subsequent comma, so that it says
"which are created using separate".

+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns or expressions in the partition key and partition
+      bound constraints associated with the individual partitions.

How about: "A data row inserted into the table is routed to a
partition based on the value of columns or expressions in the
partition key. If no existing partition matches the values in the new
row, an error will occur."

+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.

Delete "data". Add <literal> tags around the keywords.

+    tuple = SearchSysCache1(PARTEDRELID,
+                            ObjectIdGetDatum(RelationGetRelid(rel)));
+    /* Cannot already exist */
+    Assert(!HeapTupleIsValid(tuple));

This seems pointless. It's hard to see how the tuple could already
exist, but if it does, this will fail a little later on anyway when we
do simple_heap_insert() and CatalogUpdateIndexes() for the new tuple.
In general, if you're doing work only to support an Assert(), you
should put #ifdef USE_ASSERT_CHECKING around it, but in this case I'd
just rip this out.

+ myself.objectId = RelationGetRelid(rel);;

Extra semicolon.

+    /*
+     * Store dependencies on anything mentioned in the key expressions.
+     * However, ignore the column references which causes self-dependencies
+     * to be created that are undesirable.  That is done by asking the
+     * dependency-tracking sub-system to ignore any such dependencies.
+     */

I think this comment is spending a lot of time explaining the
mechanism when what it should be doing is explaining why this case
arises here and not elsewhere.

+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)

Extra space before &&.

+ if(partattno != 0)

Missing space.

+         * Identify a btree opclass to use. Currently, we use only btree
+         * operators which seems enough for list and range partitioning.

Probably best to add a comma before "which".

                 break;
             case T_ForeignKeyCacheInfo:
                 _outForeignKeyCacheInfo(str, obj);
+            case T_PartitionSpec:
+                _outPartitionSpec(str, obj);
+                break;
+            case T_PartitionElem:
+                _outPartitionElem(str, obj);
                 break;

default:

Missing break.

+ n->strategy = PARTITION_STRAT_RANGE;

Let's not abbreviate STRATEGY to STRAT in the names of these constants.

         case EXPR_KIND_TRIGGER_WHEN:
             return "WHEN";
+        case EXPR_KIND_PARTITION_KEY:
+            return "partition key expression";

I think you should say "PARTITION BY" here. See the function header
comment for ParseExprKindName.

+                 errmsg("cannot use more than one column in partition key"),
+                 errdetail("Only one column allowed with list
partitioning.")));

How about combining these two: cannot list partition using more than one column

+ * Note that the partition key data attached to a relcache entry must be
+ * 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.

Again, don't just copy existing comments. Refer to them.

+ * To retrieve further variable-length attributes, we'd need the catlog's

Typo.

+ * pg_partitioned_table_fn.h
+ *      prototypes for functions in catalog/pg_partitioned_table.c

Is it really worth having a separate header file for ONE function?

+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)

I bet you can avoid making this a keyword. PARTITION BY IDENT in the
grammar, or something like that.

+CREATE TABLE pkrel(
+    a int PRIMARY KEY
+);

Add a space.

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

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

#60Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#59)
Re: Declarative partitioning - another take

On Fri, Sep 30, 2016 at 9:10 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I removed DEPENDENCY_IGNORE. Does the following look good or am I still
missing something?

You missed your commit message, but otherwise looks fine.

I have moved this patch to next CF because that's fresh, but switched
the patch as "waiting on author". Be careful, the patch was in "needs
review".
--
Michael

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

#61Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Michael Paquier (#60)
Re: Declarative partitioning - another take

On 2016/10/03 13:26, Michael Paquier wrote:

On Fri, Sep 30, 2016 at 9:10 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I removed DEPENDENCY_IGNORE. Does the following look good or am I still
missing something?

You missed your commit message, but otherwise looks fine.

I have moved this patch to next CF because that's fresh, but switched
the patch as "waiting on author". Be careful, the patch was in "needs
review".

Thanks Michael. I was going to post the patch addressing Robert's
comments today, which I will anyway. Then I will switch the status back
to "Needs Review", albeit as part of the next CF.

Thanks,
Amit

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

#62Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#59)
9 attachment(s)
Re: Declarative partitioning - another take

Thanks for the comments.

On 2016/09/30 9:10, Robert Haas wrote:

On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Things were that way initially, that is, the parent relations had no
relfilenode. I abandoned that project however. The storage-less parent
thing seemed pretty daunting to me to handle right away. For example,
optimizer and the executor code needed to be taught about the parent rel
appendrel member that used to be included as part of the result of
scanning the inheritance set but was no longer.

Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.

I agree. So we must prevent from the get-go the creation of following
objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):

* Indexes
* Row triggers (?)

In addition to preventing creation of these objects, we must also teach
commands that directly invoke heapam.c to skip such relations.

I have not implemented that in the patch yet though.

+        /*
+         * Run the expressions through eval_const_expressions. This is
+         * not just an optimization, but is necessary, because eventually
+         * the planner will be comparing them to similarly-processed qual
+         * clauses, and may fail to detect valid matches without this.
+         * We don't bother with canonicalize_qual, however.
+         */

I'm a bit confused by this, because I would think this processing
ought to have been done before storing anything in the system
catalogs. I don't see why it should be necessary to do it again after
pulling data back out of the system catalogs.

The pattern matches what's done for other expressions that optimizer deals
with, such as CHECK, index key, and index predicate expressions.

That's kind of a non-answer answer, but OK. Still, I think you
shouldn't just copy somebody else's comment blindly into a new place.
Reference the original comment, or write your own.

Sorry I could have explained a bit more in my previous message. I rewrote
the comment as follows:

/*
* Run the expressions through const-simplification since the planner
* will be comparing them to similarly-processed qual clause operands,
* and may fail to detect valid matches without this step. We don't
* need to bother with canonicalize_qual() though, because partition
* expressions are not full-fledged qualification clauses.
*/

+    if (classform->relkind != relkind &&
+                (relkind == RELKIND_RELATION &&
+                    classform->relkind != RELKIND_PARTITIONED_TABLE))

That's broken. Note that all of the conditions are joined using &&,
so if any one of them fails then we won't throw an error. In
particular, it's no longer possible to throw an error when relkind is
not RELKIND_RELATION.

You are right. I guess it would have to be the following:

+    if ((classform->relkind != relkind &&
+         classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+        (classform->relkind == RELKIND_PARTITIONED_TABLE &&
+         relkind != RELKIND_RELATION))

Such hackishness could not be helped because we have a separate DROP
command for every distinct relkind, except we overload DROP TABLE for both
regular and partitioned tables.

Maybe this would be better:

if (relkind == RELKIND_PARTITIONED_TABLE)
syntax_relkind = RELKIND_RELATION;
else
syntax_relkind = rekind;
if (classform->relkind != syntax_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);

In this case, relkind refers to the command viz. DROP <ObjectType/relkind>
<relname>. It can never be RELKIND_PARTITIONED_TABLE, so the above will
be equivalent to leaving things unchanged. Anyway I agree the suggested
style is better, but I assume you meant:

if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
else
expected_relkind = classform->relkind;

if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);

Why isn't this logic being invoked from transformCreateStmt()? Then
we could use the actual parseState for the query instead of a fake
one.

Because we need an open relation for it to work, which in this case there
won't be until after we have performed heap_create_with_catalog() in
DefineRelation(). Mainly because we need to perform transformExpr() on
expressions. That's similar to how cookConstraint() on the new CHECK
constraints cannot be performed earlier. Am I missing something?

Hmm, yeah, I guess that's the same thing. I guess I got on this line
of thinking because the function name started with "transform" which
is usually something happening during parse analysis only. Maybe add
a code comment explaining why the work can't be done sooner, just like
what you've written here.

Added the comment.

Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
just remove the above code. Of course, we must then record the collation
in the catalog alongside other user-specified information such as operator
class. Currently, if the key is a simple column we use its attcollation
and if it's an expression then we use its exprCollation().

When I first wrote it, I wasn't sure what the implications of explicit
collations would be for partitioning. There are two places where it comes
into play: a) when comparing partition key values using the btree compare
function b) embedded as varcollid and inputcollid in implicitly generated
check constraints for partitions. In case of the latter, any mismatch
with query-specified collation causes constraint exclusion proof to be
canceled. When it's default collations everywhere, the chances of that
sort of thing happening are less. If we support user-specified collations
on keys, then things will get a little bit more involved.

I think it's worth rewinding to general principles here: the only time
a collation has real meaning is in the context of a comparison
operation. If we ask whether A < B, the answer may depend on the
collation that is used to perform the comparison. Any other place
that mentions a collation is only trying to influence the collation
that gets used for some comparison that will happen later - so, for
example, for a table column, attcollation is setting the default
collation for comparisons involving that column. The column is not
itself collated; that doesn't really make sense.

In the case of partitioning, there is really exactly one thing that
matters: everybody's got to agree on the collation to be used to
compare actual or hypothetical values of the partitioning columns
against the partition bounds. If we've got a set of partition bounds
b_1, b_2, ..., b_n and a set of partitions p_1, p_2, ..., p_{n-1} such
that a row in p_i must have a key k such that b_i < k and k < b_{i+1},
and if the meaning of that "<" operator is collation-dependent, then
we had better agree on which collation is in use every single time we
do the test.

Indexes, of course, have this exact same problem, at least if, like
btree, they rely on ordering. We search the index by applying the "<"
operator to compare various values taken from the index tuples with
the value we're trying to find, and it is absolutely vital that the
collation used for lookups remain constant and that it is the same as
the collation used for inserts, which had also better remain constant.
That is why pg_index has an indcollation column of type oidvector: it
tells us which collation to use for each index column. It pairs with
indclass, which tells us which operator class to use for each index
column. I think that partitioning will need exact analogues -
partclass and partcollation - because it has exactly the same problem.
Currently, you've got partclass but not partcollation.

I'd in general recommend that you try to follow the index precedent
here as closely as practical.

Thanks for the explanation. I added a new field to the catalog called
partcollation.

That means a couple of things - when comparing input key values (consider
a new tuple being routed) to partition bounds using the btree compare
function, we use the corresponding key column's partcollation. The same
is also used as inputcollid of the OpExpr (or ScalarArrayOpExpr) in the
implicitly generated CHECK constraints.

However, varcollid of any Var nodes in the above expressions is still the
corresponding attributes' attcollation.

Needless to say, a query-specified qualification clause must now include
the same collate clause as used for individual partition columns (or
expressions) for the constraint exclusion to work as intended.

+                            char    relkind = ((CreateStmt *)
stmt)->partby != NULL
+                                                    ? RELKIND_PARTITIONED_TABLE
+                                                    : RELKIND_RELATION;

Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
RELKIND_PARTITION_TABLE; }

Done. By the way, I made the ereport say the following: "unexpected
relkind value passed to DefineRelation", did you intend it to say
something else?

You don't need it to be translatable because it should only happen if
there's a bug in the code; users shouldn't actually be able to hit it.
So use elog() rather than ereport(). You don't need to include the
function name, either; the user can use \errverbose or \set VERBOSITY
verbose to get the file and line number if they need it. So just
elog(ERROR, "unexpected relkind") should be fine; or maybe elog(ERROR,
"unexpected relkind: %d", (int) relkind), following similar precedent
elsewhere.

Done as: elog(ERROR, "unexpected relkind: %d", (int) relkind)

Reading through the latest 0001:

+   The catalog <structname>pg_partitioned_table</structname> stores information
+   about the partition key of tables.

Maybe "stores information about how tables are partitioned".

Fixed.

+ Partitioning strategy (or method); <literal>l</> = list
partitioned table,

I don't think we need "(or method)".

Fixed.

+ <entry><structfield>partexprbin</structfield></entry>

Is there any good reason not to do partexprbin -> partexpr throughout the patch?

Agreed. Though I used partexprs (like indexprs).

+      A partitioned table is divided into sub-tables (called partitions), which
+      in turn, are created using separate <literal>CREATE TABLE</> commands.

I would delete "in turn" and the subsequent comma, so that it says
"which are created using separate".

Done.

+      The table itself is empty.  A data row inserted into the table is mapped
+      to and stored in one of the partitions (if one exists) based on the
+      values of columns or expressions in the partition key and partition
+      bound constraints associated with the individual partitions.

How about: "A data row inserted into the table is routed to a
partition based on the value of columns or expressions in the
partition key. If no existing partition matches the values in the new
row, an error will occur."

Done.

+      Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+      KEY constraints; however, you can define these constraints on individual
+      data partitions.

Delete "data". Add <literal> tags around the keywords.

Done.

+    tuple = SearchSysCache1(PARTEDRELID,
+                            ObjectIdGetDatum(RelationGetRelid(rel)));
+    /* Cannot already exist */
+    Assert(!HeapTupleIsValid(tuple));

This seems pointless. It's hard to see how the tuple could already
exist, but if it does, this will fail a little later on anyway when we
do simple_heap_insert() and CatalogUpdateIndexes() for the new tuple.
In general, if you're doing work only to support an Assert(), you
should put #ifdef USE_ASSERT_CHECKING around it, but in this case I'd
just rip this out.

OK, removed the Assert.

+ myself.objectId = RelationGetRelid(rel);;

Extra semicolon.

Fixed.

+    /*
+     * Store dependencies on anything mentioned in the key expressions.
+     * However, ignore the column references which causes self-dependencies
+     * to be created that are undesirable.  That is done by asking the
+     * dependency-tracking sub-system to ignore any such dependencies.
+     */

I think this comment is spending a lot of time explaining the
mechanism when what it should be doing is explaining why this case
arises here and not elsewhere.

Rewrote the comment as follows:

/*
* Anything mentioned in the expressions. We must ignore the column
* references which will count as self-dependency items; in this case,
* the depender is the table itself (there is no such thing as partition
* key object).
*/

+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)

Extra space before &&.

Fixed.

+ if(partattno != 0)

Missing space.

Fixed.

+         * Identify a btree opclass to use. Currently, we use only btree
+         * operators which seems enough for list and range partitioning.

Probably best to add a comma before "which".

Done.

break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+            case T_PartitionSpec:
+                _outPartitionSpec(str, obj);
+                break;
+            case T_PartitionElem:
+                _outPartitionElem(str, obj);
break;

default:

Missing break.

Oops, fixed.

+ n->strategy = PARTITION_STRAT_RANGE;

Let's not abbreviate STRATEGY to STRAT in the names of these constants.

Done.

case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+        case EXPR_KIND_PARTITION_KEY:
+            return "partition key expression";

I think you should say "PARTITION BY" here. See the function header
comment for ParseExprKindName.

Used "PARTITION BY". I was trying to mimic EXPR_KIND_INDEX_EXPRESSION,
but this seems to work better. Also, I renamed EXPR_KIND_PARTITION_KEY to
EXPR_KIND_PARTITION_EXPRESSION.

By the way, a bunch of error messages say "partition key expression". I'm
assuming it's better to leave them alone, that is, not rewrite them as
"PARTITION BY expressions".

+                 errmsg("cannot use more than one column in partition key"),
+                 errdetail("Only one column allowed with list
partitioning.")));

How about combining these two: cannot list partition using more than one column

Done.

+ * Note that the partition key data attached to a relcache entry must be
+ * 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.

Again, don't just copy existing comments. Refer to them.

Rewrote the comment to explain two points: why build in the working
context instead of directly in the CacheMemoryContext and why use a
private child context of CacheMemoryContext to store the information.

+ * To retrieve further variable-length attributes, we'd need the catlog's

Typo.

Fixed.

+ * pg_partitioned_table_fn.h
+ *      prototypes for functions in catalog/pg_partitioned_table.c

Is it really worth having a separate header file for ONE function?

Previously the two functions in this file were defined in catalog/heap.c
and I think they could be moved back there alongside such folks as
AddRelationNewConstraints, StoreAttrDefault, RemoveAttributeById,
RemoveStatistics, etc. So did.

There are no longer pg_partitioned_table.c and pg_partitioned_table_fn.h
files.

+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)

I bet you can avoid making this a keyword. PARTITION BY IDENT in the
grammar, or something like that.

Managed to do that with one more production as follows:

+part_strategy:  IDENT                   { $$ = $1; }
+                | unreserved_keyword    { $$ = pstrdup($1); }
+        ;

And then PARTITION BY part_strategy

That's because "range" is already a keyword.

+CREATE TABLE pkrel(
+    a int PRIMARY KEY
+);

Add a space.

Fixed.

Attached updated patches.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-7.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-7.patchDownload
From 0e1f52f42c4473bfec4979a14df072ad6751a0cb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  160 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |    7 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  434 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |    7 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 +++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   11 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  257 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    5 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   36 +++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   66 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   34 +++
 src/test/regress/sql/create_table.sql      |  137 +++++++++
 51 files changed, 1905 insertions(+), 50 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..893c899 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partitioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is routed
+      to a partition based on the value of columns or expressions in the
+      partition key.  If no existing partition matches the values in the new
+      row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition key, opclass info into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references which will count as self-dependency items; in this case,
+	 * the depender is the table itself (there is no such thing as partition
+	 * key object).
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
 	namespaceId = RelationGetNamespace(rel);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_MATVIEW)
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	/* Process and store partition key information, if any */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier. 
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
 	ObjectAddressSet(address, RelationRelationId, relationId);
 
 	/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (attnum == partattno)
+				return true;
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			if (is_expr)
+				*is_expr = true;
+
+			/* Find all attributes referenced */
+			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;
+
+				if (attno == attnum)
+					return true;
+			}
+			partexprs_item = lnext(partexprs_item);
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5840,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5885,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partition strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
 } DependencyType;
 
 /*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy name ('list', 'range', etc.)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	List	   *partexprs;		/* partition key expressions, if any */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation */
+	Oid		   *partcollation;
+
+	/* Type information of partition attributes */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-7.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-7.patchDownload
From 7e1985802e1946ba74d5fd0f7e086209336ba1fb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   36 ++++++--
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..51e175e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case 'l':
+			appendStringInfo(&buf, "LIST");
+			break;
+		case 'r':
+			appendStringInfo(&buf, "RANGE");
+			break;
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 299e887..e310b63 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15456,6 +15470,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15516,6 +15533,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15552,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15751,6 +15770,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-7.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-7.patchDownload
From bb0097c43241f53e86e840bc9e316d87f7d17bf1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |   33 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1772 ++++++++++++++++++++++++++++
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  774 ++++++++++--
 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                  |  208 ++++-
 src/backend/parser/parse_agg.c             |    1 -
 src/backend/parser/parse_utilcmd.c         |  366 ++++++-
 src/backend/utils/cache/relcache.c         |  105 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   59 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   42 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  188 +++
 src/test/regress/sql/alter_table.sql       |  192 +++
 src/test/regress/sql/create_table.sql      |  139 +++
 32 files changed, 4409 insertions(+), 162 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1233,6 +1315,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..007782c 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 893c899..dc07d32 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>
@@ -1424,7 +1494,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2479,7 +2498,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..05e63c3
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1772 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundList *list_spec,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundRange *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundList  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionBoundList));
+					list_spec = (PartitionBoundList *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->parttypbyval[0],
+													  key->parttyplen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundRange  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionBoundRange));
+					range_spec = (PartitionBoundRange *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				ListInfo   *listinfo;
+				int		   *mapping;
+				int			next_index = 0;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+				mapping = (int *) palloc(sizeof(int) * nparts);
+
+				/* Initialize with invalid mapping values */
+				for (i = 0; i < nparts; i++)
+					mapping[i] = -1;
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they are same for any two partitioning schemes
+				 * with the same lists. One way to achieve this is to assign
+				 * indexes in the same order as the least values of the
+				 * individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo->null_index = mapping[null_partition_index];
+				else
+					listinfo->null_index = -1;
+
+				/*
+				 * Now assign OIDs from the original array into mapped indexes
+				 * of the result array.  Order of OIDs in the former is defined
+				 * by the catalog scan that retrived them, whereas that in the
+				 * latter is defined by canonicalized representation of list
+				 * values (viz. ordering of list values).
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[mapping[i]] = oids[i];
+
+				result->bounds->listinfo = listinfo;
+				pfree(mapping);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			PartitionBoundList *list;
+
+			Assert(IsA(bound, PartitionBoundList));
+			list = (PartitionBoundList *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, list->location)));
+				}
+			}
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionBoundRange *range;
+
+			Assert(IsA(bound, PartitionBoundRange));
+			range = (PartitionBoundRange *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+	Oid		parentOID = InvalidOid;
+	Relation rel;
+
+	rel = heap_open(relid, AccessShareLock);
+	if (rel->rd_rel->relispartition)
+		parentOID = get_partition_parent(relid);
+	heap_close(rel, AccessShareLock);
+
+	if (!OidIsValid(parentOID))
+		return NIL;
+
+	return list_concat(list_make1_oid(parentOID),
+					   get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionBoundList))
+	{
+		PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionBoundRange))
+	{
+		PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionBoundList *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionBoundRange *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel)
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0], -1,
+							   get_typcollation(key->partopcintype[0]),
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *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 *nulltest;
+
+		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->parttypid[0],
+									   key->parttypmod[0],
+									   key->parttypcoll[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 */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  make_opclause(operoid, BOOLOID,
+										false,
+										(Expr *) key_col,
+										(Expr *) key_val,
+										InvalidOid,
+										key->partcollation[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	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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->partcollation[i]);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull, NULL);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		if (DatumGetBool(test_result))
+		{
+			/*
+			 * Yes, build leftop eq lower_val
+			 */
+			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->partcollation[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/* Build leftop ge/gt lower_val */
+			strategy = 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->partcollation[i]));
+
+			/* Build leftop le/lt upper_val */
+			strategy = i < 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->partcollation[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->parttypid[col],
+								  key->parttypid[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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										  key->parttypbyval[0],
+										  key->parttyplen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+										  key->parttyplen[i]);
+			i++;
+		}
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+									   key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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->partcollation[i],
+												 val1[i], val2[i]));
+		if (result != 0)
+			break;
+	}
+
+	return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -163,6 +164,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 */
+	List	   *partition_quals; /* partition check quals for attach partition
+								  * validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,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
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
 	 * but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1471,7 +1514,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;
@@ -1516,8 +1560,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),
@@ -1544,6 +1588,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),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,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;
@@ -1856,6 +1932,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)
 	{
@@ -1885,16 +1964,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);
@@ -1931,8 +2014,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 */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2600,11 +2689,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2846,8 +2936,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3126,6 +3219,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);
@@ -3443,6 +3541,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);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3762,6 +3872,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);
@@ -3947,7 +4063,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7892,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);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,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;
 
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,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);
 
@@ -10332,16 +10545,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;
 }
 
 /*
@@ -10392,7 +10597,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
@@ -10410,12 +10615,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10455,8 +10668,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10485,7 +10702,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.
@@ -10504,10 +10721,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10610,13 +10878,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];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,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);
 
@@ -10668,11 +10925,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
@@ -10681,7 +10947,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)))
@@ -10743,7 +11009,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);
 
@@ -10774,7 +11040,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)
@@ -10786,30 +11052,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;
 }
 
 /*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+	PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+	PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+	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
  * ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundList:
+			retval = _copyPartitionBoundList(from);
+			break;
+		case T_PartitionBoundRange:
+			retval = _copyPartitionBoundRange(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 a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,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);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *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
  */
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundList:
+			retval = _equalPartitionBoundList(a, b);
+			break;
+		case T_PartitionBoundRange:
+			retval = _equalPartitionBoundRange(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 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -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);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *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'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundList:
+				_outPartitionBoundList(str, obj);
+				break;
+			case T_PartitionBoundRange:
+				_outPartitionBoundRange(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+	READ_LOCALS(PartitionBoundList);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+	READ_LOCALS(PartitionBoundRange);
+
+	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
@@ -2497,6 +2526,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionBoundList();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionBoundRange();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		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.
@@ -572,7 +580,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
@@ -588,7 +596,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
@@ -602,7 +611,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
 
@@ -2374,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:
@@ -2469,6 +2510,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionBoundList *n = makeNode(PartitionBoundList);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+					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); }
+		;
 
 /*****************************************************************************
  *
@@ -2886,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 OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2973,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;
@@ -2994,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;
@@ -4551,6 +4707,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11156,6 +11354,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;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13843,6 +14044,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 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..f565a5d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +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 */
@@ -130,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);
 
 
 /*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partspec != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,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->partspec)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -359,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);
@@ -898,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;
@@ -1117,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;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,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;
@@ -3026,3 +3059,334 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+	PartitionBoundList  *list, *result_list;
+	PartitionBoundRange *range, *result_range;
+
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (!IsA(bound, PartitionBoundList))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionBoundList *) bound;
+			result_list = makeNode(PartitionBoundList);
+
+			foreach(cell, list->values)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\""
+							" of key column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+					 parser_errposition(cxt->pstate, exprLocation(value))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				result_list->values = lappend(result_list->values, value);
+			}
+			return (Node *) result_list;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			char	*colname;
+
+			if (!IsA(bound, PartitionBoundRange))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionBoundRange *) bound;
+
+			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(PartitionBoundRange);
+			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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(cxt->pstate, exprLocation(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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								colname),
+							 parser_errposition(cxt->pstate, exprLocation(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 e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2609,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 */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..ea7806e
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundList,
+	T_PartitionBoundRange,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1562,7 +1597,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
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 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/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..e0181c4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
 Partition key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 5a0d933..7255690 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a 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

0004-psql-and-pg_dump-support-for-partitions-7.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-7.patchDownload
From f8560f1ff9139cff0ec7536fc195fdb4bc98a950 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++++----
 src/test/regress/expected/create_table.out |   39 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 ++++
 7 files changed, 393 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 51e175e..c76ebb1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundList:
+			{
+				PartitionBoundList *list_spec = (PartitionBoundList *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionBoundRange:
+			{
+				PartitionBoundRange *range_spec = (PartitionBoundRange *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e310b63..4788018 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6141,6 +6141,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15321,6 +15378,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15349,8 +15417,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15379,7 +15450,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15444,15 +15516,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15622,6 +15701,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index db6dc5c..9834599 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e0181c4..19257d6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7255690..fecae9b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-7.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-7.patchDownload
From 85a3d3f401a53f412fe3f0b82a4e3a2a0bf03c5b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.

Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
 src/backend/optimizer/plan/initsplan.c |   17 ++-
 src/backend/optimizer/prep/prepunion.c |  282 +++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 3 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Do not flatten the inheritance hierarchy if partitioned table, unless
+	 * this is the result relation.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rti != root->parse->resultRelation)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
-		 */
-		if (childOID != parentOID)
-		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
-		}
-
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (oldrc)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-7.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-7.patchDownload
From 017789dfcd8fd1c8634f0c2b71857ad6b3706a20 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-7.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-7.patchDownload
From 17652ea80aad319bd505519e0ab7768e3652f3ab Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  206 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 211 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 05e63c3..402eb9e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionDesc() */
 static int32 list_value_cmp(const void *a, const void *b, void *arg);
 static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -774,6 +833,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1423,6 +1529,106 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index ea7806e..1ecd5d6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -56,4 +57,8 @@ extern List *get_partition_ancestors(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-7.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-7.patchDownload
From 22253a61d816157399060847dcfec1ccfe22edc6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  343 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  123 +++++++++++
 src/backend/optimizer/plan/createplan.c |   59 ++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 14 files changed, 907 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 402eb9e..873ee0d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * RelationBuildPartitionDesc
@@ -1545,7 +1560,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1629,6 +1644,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -1976,3 +2252,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44c273c..8c7e5f2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1729,6 +1825,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2299,7 +2413,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2455,10 +2572,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2510,6 +2663,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2530,7 +2708,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2560,7 +2747,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2593,7 +2787,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..cb47035 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,79 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				/* As many fdw_private's in fdwPrivLists as FDW partitions */
+				List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2063,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..7364211 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,6 +6152,65 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+
+		/* To force FDW driver fetch the intended RTE */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1ecd5d6..b6607d8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -61,4 +63,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 697c90f..6e51773 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1141,6 +1142,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-7.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-7.patchDownload
From a8f7ad4e6557da09378f22699480f0c1d8657766 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f43352c..8084029 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#63Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#62)
Re: Declarative partitioning - another take

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.

I agree. So we must prevent from the get-go the creation of following
objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):

* Indexes
* Row triggers (?)

Hmm, do we ever fire triggers on the parent for operations on a child
table? Note this thread, which seems possibly relevant:

/messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com

We certainly won't fire the parent's per-row triggers ever, so those
should be prohibited. But I'm not sure about per-statement triggers.

In addition to preventing creation of these objects, we must also teach
commands that directly invoke heapam.c to skip such relations.

I'm don't think that's required if we're not actually getting rid of
the relfilenode.

In this case, relkind refers to the command viz. DROP <ObjectType/relkind>
<relname>. It can never be RELKIND_PARTITIONED_TABLE, so the above will
be equivalent to leaving things unchanged. Anyway I agree the suggested
style is better, but I assume you meant:

if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
else
expected_relkind = classform->relkind;

if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);

Uh, yeah, probably that's what I meant. :-)

Thanks for the explanation. I added a new field to the catalog called
partcollation.

That means a couple of things - when comparing input key values (consider
a new tuple being routed) to partition bounds using the btree compare
function, we use the corresponding key column's partcollation. The same
is also used as inputcollid of the OpExpr (or ScalarArrayOpExpr) in the
implicitly generated CHECK constraints.

Check.

However, varcollid of any Var nodes in the above expressions is still the
corresponding attributes' attcollation.

I think that's OK.

Needless to say, a query-specified qualification clause must now include
the same collate clause as used for individual partition columns (or
expressions) for the constraint exclusion to work as intended.

Hopefully this is only required if the default choice of collation
wouldn't be correct anyway.

+ <entry><structfield>partexprbin</structfield></entry>

Is there any good reason not to do partexprbin -> partexpr throughout the patch?

Agreed. Though I used partexprs (like indexprs).

Oh, good idea.

case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+        case EXPR_KIND_PARTITION_KEY:
+            return "partition key expression";

I think you should say "PARTITION BY" here. See the function header
comment for ParseExprKindName.

Used "PARTITION BY". I was trying to mimic EXPR_KIND_INDEX_EXPRESSION,
but this seems to work better. Also, I renamed EXPR_KIND_PARTITION_KEY to
EXPR_KIND_PARTITION_EXPRESSION.

By the way, a bunch of error messages say "partition key expression". I'm
assuming it's better to leave them alone, that is, not rewrite them as
"PARTITION BY expressions".

I think that is fine. ParseExprKindName is something of a special case.

Reviewing 0002:

+    prettyFlags = PRETTYFLAG_INDENT;
+    PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+                                    prettyFlags)));

Why bother with the variable?

+    /* Must get partclass, and partexprs the hard way */
+    datum = SysCacheGetAttr(PARTEDRELID, tuple,
+                            Anum_pg_partitioned_table_partclass, &isnull);
+    Assert(!isnull);
+    partclass = (oidvector *) DatumGetPointer(datum);

Comment mentions getting two things, but code only gets one.

+ partexprs = (List *) stringToNode(exprsString);

if (!IsA(partexprs, List)) elog(ERROR, ...); so as to guard against
corrupt catalog contents.

+    switch (form->partstrat)
+    {
+        case 'l':
+            appendStringInfo(&buf, "LIST");
+            break;
+        case 'r':
+            appendStringInfo(&buf, "RANGE");
+            break;
+    }

default: elog(ERROR, "unexpected partition strategy: %d", (int)
form->partstrat);

Also, why not case PARTITION_STRATEGY_LIST: instead of case 'l': and
similarly for 'r'?

@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
                           "OR %s IS NOT NULL "
                           "OR %s IS NOT NULL"
                           "))"
-                          "AS changed_acl "
+                          "AS changed_acl, "
+                          "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
                           "FROM pg_class c "
                           "LEFT JOIN pg_depend d ON "
                           "(c.relkind = '%c' AND "

I think if you test it you'll find this breaks dumps from older server
versions. You've got to add a new version of the SQL query for 10+,
and then update the 9.6, 9.5, 9.4, 9.3, 9.1-9.2, 9.0, 8.4, 8.2-8.3,
8.0-8.1, 7.3-7.4, 7.2, 7.1, and pre-7.1 queries to return a dummy NULL
value for that column. Alternatively, you can add the new version for
10.0, leave the older queries alone, and then adjust the code further
down to cope with i_partkeydef == -1 (see i_checkoption for an
example).

gettext_noop("table"),
+ gettext_noop("table"),

Add a comment like /* partitioned table */ on the same line.

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

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

#64Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#62)
Re: Declarative partitioning - another take

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ latest patch set ]

Reviewing 0003:

+ This form attaches an existing table (partitioned or otherwise) as

(which might itself be partitioned)

+      partition of the target table.  Partition bound specification must
+      correspond with the partition method and the key of the target table.

The partition bound specification must correspond to the partitioning
method and partitioning key of the target table.

+      The table being attached must have all the columns of the target table
+      with matching types and no more. Also, it must have all the matching

The table to be attached must have all of the same columns as the
target table and no more; moreover, the column types must also match.

+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO
INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.

Why all of these requirements? We could instead perform a scan to
validate that the constraints are met. I think the way this should
work is:

1. ATTACH PARTITION works whether matching NOT NULL and CHECK
constraints are present or not.

2. If all of the constraints are present, and a validated constraint
matching the implicit partitioning constraint is also present, then
ATTACH PARTITION does not scan the table to validate constraints;
otherwise, it does.

3. NO VALIDATE is not an option.

+      Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered, but that
+      might change in the future.

Really? Changing that sounds impractical to me.

+      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 target table.

continues to exist as a standalone table, but no longer has any ties
to the table from which it was detached.

+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.

You don't really need to say this, I think. All of the properties of
the detached table are retained, not only its partitioning status.
You wouldn't like it if I told you to document "note that if a
partition being detached is unlogged, it will still be unlogged".

    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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the table.

To add the table as a new child of a parent table, or as a new
partition of a partitioned table, you must own the parent table as
well.

+ The name of the table to attach as a new partition to or
detach from this table.

s/to or/or to/

+ <literal>NO VALIDATE</> option is spcified.

Typo, but see comments above about nuking this altogether.

     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.

This is a hairy explanation. I suggest that the documentation say
this (and that the code implement it): A nonrecursive DROP TABLE
command will fail for a partitioned table, because all partitions of a
table must have the same columns as the partitioning root.

-    that are not marked <literal>NO INHERIT</>.
+    that are not marked <literal>NO INHERIT</> which are unsupported if
+    the table is a partitioned table.

I think you can omit this hunk.

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

I'd delete the sentence beginning with "However".

+   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</>:

s/, that is/ as a/

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

I think you can inline the definitions of list_spec and range_spec
here instead of making them separate productions, and I think that
would be preferable.

FOR VALUES { IN ( <replaceable
class="PARAMETER">expression</replaceable> [, ...] ) |
START <replaceable class="PARAMETER">lower-bound</replaceable> [
INCLUSIVE | EXCLUSIVE ] END <replaceable
class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
}

+ parent table (name optionally schema-qualified).

Parenthetical phrase isn't needed.

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

The partition bound specification must correspond to the partitioning
method and partitioning key of the parent table, and must not overlap
with any existing partition of that parent.

+      clause, if any.  Defaults and constraints can optionally be specified
+      for each of the inherited columns, which override those in the parent.

Surely not. You can't "override" an inherited constraint or an
inherited default. The child may have extra constraints not present
in the parent, and may have different defaults when it is directly
targeted by an insertion, but it can't possibly override the parent
defaults.

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

The first part of this seems right, but then what's going on with the
reference to constraints in the previous sentence? (See previous
review comment.) The second sentence I would delete (but see below).

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

How about: Rows inserted into a partitioned table will be
automatically routed to the correct partition. If no suitable
partition exists, an error will occur.

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

How about: A partition must have the same column names and types as
the table of which it is a partition. Therefore, modifications the
column names or types of the partitioned table will automatically
propagate to all children, as will operations such as TRUNCATE which
normally affect a table and all of its inheritance children. It is
also possible to TRUNCATE a partition individually, just as for an
inheritance child.

Insisting that you can't drop a child without detaching it first seems
wrong to me. If I already made this comment and you responded to it,
please point me back to whatever you said. However, my feeling is
this is flat wrong and absolutely must be changed.

-            if (is_no_inherit)
+
+            /* Discard the NO INHERIT flag if the relation is a partition */
+            if (is_no_inherit && !rel->rd_rel->relispartition)

Something about this seems fishy. In StoreRelCheck(), you disallow
the addition of a NO INHERIT constraint on a partition, but here you
seem happy to accept it and ignore the NO INHERIT property. That
doesn't seem consistent. We should adopt a consistent policy about
what to do about such constraints, and I submit that throwing an error
is better than silently changing things, unless you have some reason
for doing that which I'm not seeing. Anyway, we should have the same
behavior in both cases.

We should also standardize on what value of conislocal and coninhcount
children end up with; presumably the latter should be 1, but I'm not
sure if the former should be true or false. In either case, anything
that can vary between children probably needs to be dumped, so let's
enforce that it doesn't so we don't have to dump it. I'm not sure
whether the code here achieves those objectives, though, and note the
comment in the function header about making sure the logic here
matches MergeConstraintsIntoExisting.

I think the overriding principle here should be: If you attach a table
as a partition, it must not be part of a standard inheritance
hierarchy, and it must not be a partition of any other table. It can,
however, be partitioned itself. If you later detach a partition, it
ends up as a standalone table with a copy of each constraint it had as
a partition - probably including the implicit partition constraint.
The DBA can drop those constraints if they're not wanted.

I wonder if it's really a good idea for the partition constraints to
be implicit; what is the benefit of leaving those uncatalogued?

+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+    struct ListInfo       *listinfo;
+    struct RangeInfo   *rangeinfo;
+} BoundCollectionData;

This seems like an odd design. First, when you have a pointer to
either of two things, the normal tool for that in C would be a union,
not a struct. Second, in PostgreSQL we typically handle that by making
both of the things nodes and then you can use IsA() or switch on
nodeTag() to figure out what you've got. Third, the only place this
is used at least in 0003 is as part of PartitionDescData, which only
has 3 members, so if you were going to do it with 2 members, you could
just include these two members directly. Considering all of the
foregoing, I'd suggest making this a union and including partstrategy
in PartitionDescData.

I think that the names ListInfo and RangeInfo are far too generic for
something that's specific to partitioning.

+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+    struct PartitionRange    **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+    struct PartitionRangeBound    *lower;
+    struct PartitionRangeBound    *upper;
+} PartitionRange;

This representation doesn't seem optimal, because in most cases the
lower bound of one partition will be the upper bound of the next. I
suggest that you flatten this representation into a single list of
relevant bounds, each flagged as to whether it is exclusive and
whether it is finite; and a list with one more element of bounds. For
example, suppose the partition bounds are [10, 20), [20, 30), (30,
40), and [50, 60). You first construct a list of all of the distinct
bounds, flipping inclusive/exclusive for the lefthand bound in each
case. So you get:

10 EXCLUSIVE
20 EXCLUSIVE
30 EXCLUSIVE
30 INCLUSIVE
40 EXCLUSIVE
50 EXCLUSIVE
60 EXCLUSIVE

When ordering items for this list, if the same item appears twice, the
EXCLUSIVE copy always appears before INCLUSIVE. When comparing
against an EXCLUSIVE item, we move to the first half of the array if
we are searching for a value strictly less than that value; when
comparing against an INCLUSIVE item, we move to the first half of the
array if we are searching for a value less than or equal to that
value.

This is a list of seven items, so a binary search will return a
position between 0 (less than all items) and 7 (greater than all
items). So we need a list of 8 partition mappings, which in this case
will look like this: -1, 0, 1, -1, 2, -1, 3, -1.

In this particular example, there are only two adjacent partitions, so
we end up with 7 bounds with this representation vs. 8 with yours, but
in general I think the gains will be bigger. If you've got 1000
partitions and they're all adjacent, which is likely, you store 1000
bounds instead of 2000 bounds by doing it this way.

+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.

Why? How about "Because this function assumes that the relation whose
OID is passed as an argument will have precisely one parent, it should
only been called when it is known that the relation is a partition."

+    /*
+     * Translate vars in the generated expression to have correct attnos.
+     * Note that the vars in my_qual bear attnos dictated by key which carries
+     * physical attnos of the parent.  We must allow for a case where physical
+     * attnos of a partition can be different from the parent.
+     */
+    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_qual = (List *) translate_var_attno((Node *) my_qual,
+                                                       attno,
+                                                       new_attno);

It can't really be safe to do this one attribute number at a time, or
even if by some good fortune it can't be broken, it at least it seems
extremely fragile. Suppose that you translate 0 -> 3 and then 3 -> 5;
now the result is garbage. It's not very efficient to do this one
attno at a time, either.

+    if (classform->relispartition)
+        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.")));
+

RangeVarCallbackForDropRelation should do only minimal sanity checks;
defer this until after we have a relation lock.

I didn't get all the way through this patch, so this is a pretty
incomplete review, but it's late here and I'm out of steam for
tonight. Some general comments:

1. I think that this patch seems to introduce an awful lot of new
structures with confusingly similar names and purposes:
PartitionBoundList, PartitionBoundRange, ListInfo, RangeInfo,
PartitionRange, PartitionList, ListValue, RangePartition. You've got
4 different structures here that all have "Partition" and "Range" in
the name someplace, including both PartitionRange and RangePartition.
Maybe there's no way around that kind of duplication; after all there
are quite a few moving parts here. But it seems like it would be good
to try to simplify it.

2. I'm also a bit concerned about the fairly large amount of
apparently-boilerplate code in partition.c, all having to do with how
we create all of these data structures and translate between different
forms of them. I haven't understood that stuff thoroughly enough to
have a specific idea about how we might be able to get rid of any of
it, and maybe there's no way. But that too seems like a topic for
futher investigation. One idea is that maybe some of these things
should be nodes that piggyback on the existing infrastructure in
src/backend/nodes instead of inventing a new way to do something
similar.

3. There are a lot of places where you have separate code for the
range and list partitioning cases, and I'm suspicious that there are
ways that code could be unified. For example, with list partitioning,
you have a bunch of Datums which represent the specific values that
can appear in the various partitions, and with range partitioning, you
have a bunch of Datums that represent the edges of partitions. Well,
if you used the same array for both purposes, you could use the same
code to copy it. That would involve flattening away a lot of the
subsidiary structure under PartitionDescData and pulling up stuff that
is buried lower down into the main structure, but I think that's
likely a good idea anyway - see also point #1.

4. I'm somewhat wondering if we ought to just legislate that the lower
bound is always inclusive and the upper bound is always exclusive.
The decision to support both inclusive and exclusive partition bounds
is responsible for an enormous truckload of complexity in this patch,
and I have a feeling it is also going to be a not-infrequent cause of
user error.

5. I wonder how well this handles multi-column partition keys. You've
just got one Datum flag and one is-finite flag per partition, but I
wonder if you don't need to track is-finite on a per-column basis, so
that you could partition on (a, b) and have the first partition go up
to (10, 10), the second to (10, infinity), the third to (20, 10), the
fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
Oracle supports this sort of thing, so perhaps we should, too. On a
related note, I'm not sure it's going to work to treat a composite
partition key as a record type. The user may want to specify a
separate opfamily and collation for each column, not just inherit
whatever the record behavior is. I'm not sure if that's what you are
doing, but the relcache structures don't seem adapted to storing one
Datum per partitioning column per partition, but rather just one Datum
per partition, and I'm not sure that's going to work very well.

Thanks for working on this.

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

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

#65Petr Jelinek
petr@2ndquadrant.com
In reply to: Robert Haas (#64)
Re: Declarative partitioning - another take

On 05/10/16 03:50, Robert Haas wrote:

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ latest patch set ]

Reviewing 0003:

+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO
INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.

Why all of these requirements? We could instead perform a scan to
validate that the constraints are met. I think the way this should
work is:

I think it's survivable limitation in initial version, it does not seem
to me like there is anything that prevents improving this in some follow
up patch.

+      Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered, but that
+      might change in the future.

Really? Changing that sounds impractical to me.

Which part of that statement?

+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.

You don't really need to say this, I think. All of the properties of
the detached table are retained, not only its partitioning status.
You wouldn't like it if I told you to document "note that if a
partition being detached is unlogged, it will still be unlogged".

I think this is bit different though, it basically means you are
detaching whole branch from the rest of the partitioning tree, not just
that one partition. To me that's worth mentioning to avoid potential
confusion.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

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

#66Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#62)
Re: Declarative partitioning - another take

On Tue, Oct 4, 2016 at 1:32 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Attached updated patches.

Thanks,
Amit

Hi,

I observed, when creating foreign table with range partition, data is not
inserting into specified partition range. below are steps to reproduce.

CREATE EXTENSION postgres_fdw;
CREATE SERVER pwj_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname
'postgres', port '5432',use_remote_estimate 'true');
CREATE USER MAPPING FOR PUBLIC SERVER pwj_server;

CREATE TABLE test_range (a int) PARTITION BY RANGE(a);

CREATE TABLE test_range_p1 (a int);
CREATE FOREIGN TABLE ft_test_range_p1 PARTITION OF test_range FOR VALUES
START (1) END (10) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p1');

CREATE TABLE test_range_p2 (a int);
CREATE FOREIGN TABLE ft_test_range_p2 PARTITION OF test_range FOR VALUES
START (20) END (30) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p2');

CREATE TABLE test_range_p3 (a int);
CREATE FOREIGN TABLE ft_test_range_p3 PARTITION OF test_range FOR VALUES
START (10) END (20) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p3');

postgres=# INSERT INTO test_range (a) values (5),(25),(15);
INSERT 0 3

postgres=# select tableoid::regclass, * from test_range;
tableoid | a
------------------+----
ft_test_range_p1 | 5
ft_test_range_p2 | 15
ft_test_range_p3 | 25
(3 rows)

--Here ft_test_range_p2 is created for range 20-30 having value 15.

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#67Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#66)
Re: Declarative partitioning - another take

Hi,

On 2016/10/05 16:57, Rajkumar Raghuwanshi wrote:

I observed, when creating foreign table with range partition, data is not
inserting into specified partition range. below are steps to reproduce.

[ ... ]

postgres=# INSERT INTO test_range (a) values (5),(25),(15);
INSERT 0 3

postgres=# select tableoid::regclass, * from test_range;
tableoid | a
------------------+----
ft_test_range_p1 | 5
ft_test_range_p2 | 15
ft_test_range_p3 | 25
(3 rows)

--Here ft_test_range_p2 is created for range 20-30 having value 15.

Thanks a lot for testing.

That's a bug. I found that it is caused by the FDW plans getting paired
with the wrong result relations during ExecInitModifyTable() initialization.

I will include a fix for the same in the patch set that I will be sending
soon in reply to Robert's review comments on patch 0002 [1]/messages/by-id/CA+TgmoY1aQ5iPz0S2GBJw4YUR1Z2Qg5iKUf8YJSo2Ctya4ZmNg@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoY1aQ5iPz0S2GBJw4YUR1Z2Qg5iKUf8YJSo2Ctya4ZmNg@mail.gmail.com
/messages/by-id/CA+TgmoY1aQ5iPz0S2GBJw4YUR1Z2Qg5iKUf8YJSo2Ctya4ZmNg@mail.gmail.com

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

#68Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#63)
9 attachment(s)
Re: Declarative partitioning - another take

On 2016/10/05 2:12, Robert Haas wrote:

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.

I agree. So we must prevent from the get-go the creation of following
objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):

* Indexes
* Row triggers (?)

Hmm, do we ever fire triggers on the parent for operations on a child
table? Note this thread, which seems possibly relevant:

/messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com

The answer to your question is no.

The thread you quoted discusses statement-level triggers and the
conclusion is that they don't work as desired for UPDATE and DELETE on
inheritance tables. As things stand, only UPDATE or DELETE on the parent
affects the child tables and it's proposed there that the statement-level
triggers on the parent and also on any child tables affected should be
fired in that case.

Currently, INSERT doesn't have that problem because it only ever affects
the parent table. That changes with tuple routing though. I am not sure
if the aforementioned proposed behavior should be applied in this case.
That is to say, if we have INSERT INTO parent, only the parent's
statement-level triggers should be fired. The partition into which the
tuple is routed will have only its row-level triggers fired.

The proposed TODO item has been left untouched for many years now, so it
is perhaps not such a big pain point after all (or perhaps some standard
noncompliance). I may be wrong though.

We certainly won't fire the parent's per-row triggers ever, so those
should be prohibited. But I'm not sure about per-statement triggers.

Agree about the row-level triggers. I think per-statement triggers are
alright to allows on parent tables from the POV of the original topic we
were discussing - ie, per-statement triggers do not depend on the physical
addressing capabilities of the relation on which they are defined whereas
row-levels triggers do in some cases.

So I changed 0001 so that indexes and row triggers are disallowed on
partitioned tables.

In addition to preventing creation of these objects, we must also teach
commands that directly invoke heapam.c to skip such relations.

I'm don't think that's required if we're not actually getting rid of
the relfilenode.

Oops that line got sent after all, I had convinced myself to remove that
line from the email. :) Agreed, no need to do that just yet.

Needless to say, a query-specified qualification clause must now include
the same collate clause as used for individual partition columns (or
expressions) for the constraint exclusion to work as intended.

Hopefully this is only required if the default choice of collation
wouldn't be correct anyway.

Sorry, I'm not sure I understand what you said - specifically what would
"wrong" mean in this case? Parser error? Run-time error like in varstr_cmp()?

What I meant to say is that predicate OpExpr's generated by the
partitioning code will be using the corresponding partition column's
partcollation as its inputcollid (and the Var will be wrapped by a
RelabelType). Here partcollation is a known valid alternative collation
that is applied when partitioning the input data, overriding any default
collation of the column's type or the collation specified for the column
when creating the table. During assign_query_collations(), a qual clause
OpExpr's variable will get assigned a collation that is either the
column/expressions's attcollation/exprCollationor explicitly specified
collation using a COLLATE clause. If variable collation assigned thusly
doesn't match the partitioning collation, then the predicate OpExpr's and
the query clause OpExpr's variable expressions will fail to match using
equal(), so constraint exclusion (specifically, operator_predicate_proof()
bails out) will fail. Of course, going ahead with constraint exclusion
ignoring the said collation mismatch would be wrong anyway.

Reviewing 0002:

+    prettyFlags = PRETTYFLAG_INDENT;
+    PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+                                    prettyFlags)));

Why bother with the variable?

Leftover, removed.

+    /* Must get partclass, and partexprs the hard way */
+    datum = SysCacheGetAttr(PARTEDRELID, tuple,
+                            Anum_pg_partitioned_table_partclass, &isnull);
+    Assert(!isnull);
+    partclass = (oidvector *) DatumGetPointer(datum);

Comment mentions getting two things, but code only gets one.

Fixed. Further code uses the "hard way" to get partexprs but it's got a
separate comment anyway.

+ partexprs = (List *) stringToNode(exprsString);

if (!IsA(partexprs, List)) elog(ERROR, ...); so as to guard against
corrupt catalog contents.

Done as follows:

partexprs = (List *) stringToNode(exprsString);

if (!IsA(partexprs, List))
elog(ERROR, "unexpected node type found in partexprs: %d",
(int) nodeTag(partexprs));

+    switch (form->partstrat)
+    {
+        case 'l':
+            appendStringInfo(&buf, "LIST");
+            break;
+        case 'r':
+            appendStringInfo(&buf, "RANGE");
+            break;
+    }

default: elog(ERROR, "unexpected partition strategy: %d", (int)
form->partstrat);

Also, why not case PARTITION_STRATEGY_LIST: instead of case 'l': and
similarly for 'r'?

Done and done.

@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL "
"OR %s IS NOT NULL"
"))"
-                          "AS changed_acl "
+                          "AS changed_acl, "
+                          "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "

I think if you test it you'll find this breaks dumps from older server
versions. You've got to add a new version of the SQL query for 10+,
and then update the 9.6, 9.5, 9.4, 9.3, 9.1-9.2, 9.0, 8.4, 8.2-8.3,
8.0-8.1, 7.3-7.4, 7.2, 7.1, and pre-7.1 queries to return a dummy NULL
value for that column. Alternatively, you can add the new version for
10.0, leave the older queries alone, and then adjust the code further
down to cope with i_partkeydef == -1 (see i_checkoption for an
example).

You're quite right. Fixed using this last method.

gettext_noop("table"),
+ gettext_noop("table"),

Add a comment like /* partitioned table */ on the same line.

Done.

Attached revised patches. Also, includes a fix for an issue reported by
Rajkumar Raghuwanshi [1]/messages/by-id/5dded2f1-c7f6-e7fc-56b5-23ab59495e4b@lab.ntt.co.jp which turned out to be a bug in one of the later
patches. I will now move on to addressing the comments on patch 0003.

Thanks a lot for the review!

Thanks,
Amit

[1]: /messages/by-id/5dded2f1-c7f6-e7fc-56b5-23ab59495e4b@lab.ntt.co.jp
/messages/by-id/5dded2f1-c7f6-e7fc-56b5-23ab59495e4b@lab.ntt.co.jp

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-8.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-8.patchDownload
From 35c961198e923f826a851f79bc7c9b7f18c77666 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  160 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |    9 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  434 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 +++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   11 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  257 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    5 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   36 +++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   66 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   34 +++
 src/test/regress/sql/create_table.sql      |  137 +++++++++
 51 files changed, 1915 insertions(+), 49 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..893c899 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partitioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is routed
+      to a partition based on the value of columns or expressions in the
+      partition key.  If no existing partition matches the values in the new
+      row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition key, opclass info into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references which will count as self-dependency items; in this case,
+	 * the depender is the table itself (there is no such thing as partition
+	 * key object).
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	/* Process and store partition key information, if any */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier. 
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
 	ObjectAddressSet(address, RelationRelationId, relationId);
 
 	/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (attnum == partattno)
+				return true;
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			if (is_expr)
+				*is_expr = true;
+
+			/* Find all attributes referenced */
+			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;
+
+				if (attno == attnum)
+					return true;
+			}
+			partexprs_item = lnext(partexprs_item);
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5840,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5885,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partition strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
 } DependencyType;
 
 /*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy name ('list', 'range', etc.)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	List	   *partexprs;		/* partition key expressions, if any */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation */
+	Oid		   *partcollation;
+
+	/* Type information of partition attributes */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-8.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-8.patchDownload
From f555a191b3cfb74c472e721f5a1eb5ccf2f7d9c3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  146 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  134 ++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 366 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..82f03ea 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,150 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 299e887..8aa615b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5235,7 +5240,108 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90600)
+	if (fout->remoteVersion >= 100000)
+	{
+		PQExpBuffer acl_subquery = createPQExpBuffer();
+		PQExpBuffer racl_subquery = createPQExpBuffer();
+		PQExpBuffer initacl_subquery = createPQExpBuffer();
+		PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+		PQExpBuffer attacl_subquery = createPQExpBuffer();
+		PQExpBuffer attracl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 *
+		 * Left join to detect if any privileges are still as-set-at-init, in
+		 * which case we won't dump out ACL commands for those.
+		 */
+
+		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+						initracl_subquery, "c.relacl", "c.relowner",
+				 "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+						dopt->binary_upgrade);
+
+		buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+					  attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+						dopt->binary_upgrade);
+
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "%s AS relacl, %s as rrelacl, "
+						  "%s AS initrelacl, %s as initrrelacl, "
+						  "c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relrowsecurity, c.relforcerowsecurity, "
+						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "tc.relminmxid AS tminmxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "c.relreplident, c.relpages, "
+						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+						  "d.refobjid AS owning_tab, "
+						  "d.refobjsubid AS owning_col, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						  "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+						  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+						  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+						  "tc.reloptions AS toast_reloptions, "
+						  "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = at.attnum)"
+						  "WHERE at.attrelid = c.oid AND ("
+						  "%s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL"
+						  "))"
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+						  "LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = 0) "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  acl_subquery->data,
+						  racl_subquery->data,
+						  initacl_subquery->data,
+						  initracl_subquery->data,
+						  username_subquery,
+						  attacl_subquery->data,
+						  attracl_subquery->data,
+						  attinitacl_subquery->data,
+						  attinitracl_subquery->data,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
+
+		destroyPQExpBuffer(acl_subquery);
+		destroyPQExpBuffer(racl_subquery);
+		destroyPQExpBuffer(initacl_subquery);
+		destroyPQExpBuffer(initracl_subquery);
+
+		destroyPQExpBuffer(attacl_subquery);
+		destroyPQExpBuffer(attracl_subquery);
+		destroyPQExpBuffer(attinitacl_subquery);
+		destroyPQExpBuffer(attinitracl_subquery);
+	}
+	else if (fout->remoteVersion >= 90600)
 	{
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5884,6 +5990,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +6061,10 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+			tblinfo[i].partkeydef = NULL;
+		else
+			tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5998,7 +6109,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6213,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15456,6 +15572,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15516,6 +15635,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15654,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15751,6 +15872,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-8.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-8.patchDownload
From 8ff8169bbc9f2e20a3c1711c19b9e7fe5e531d92 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  111 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  105 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |   33 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1760 ++++++++++++++++++++++++++++
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  774 ++++++++++--
 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                  |  208 ++++-
 src/backend/parser/parse_agg.c             |    1 -
 src/backend/parser/parse_utilcmd.c         |  366 ++++++-
 src/backend/utils/cache/relcache.c         |  105 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   58 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   42 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  188 +++
 src/test/regress/sql/alter_table.sql       |  192 +++
 src/test/regress/sql/create_table.sql      |  139 +++
 32 files changed, 4396 insertions(+), 162 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (partitioned or otherwise) 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
+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.
+      Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </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 target table.
+     </para>
+     <para>
+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +775,9 @@ 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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the 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
@@ -938,6 +993,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>
 
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     A recursive <literal>DROP COLUMN</literal> operation will remove a
     descendant table's column only if the descendant does not inherit
     that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.
    </para>
 
    <para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     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</> which are unsupported if
+    the table is a partitioned table.
    </para>
 
    <para>
@@ -1233,6 +1315,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..007782c 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 893c899..dc07d32 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>
@@ -1424,7 +1494,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2479,7 +2498,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..047249f
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1760 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundList *list_spec,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundRange *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundList  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionBoundList));
+					list_spec = (PartitionBoundList *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->parttypbyval[0],
+													  key->parttyplen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundRange  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionBoundRange));
+					range_spec = (PartitionBoundRange *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				ListInfo   *listinfo;
+				int		   *mapping;
+				int			next_index = 0;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+				mapping = (int *) palloc(sizeof(int) * nparts);
+
+				/* Initialize with invalid mapping values */
+				for (i = 0; i < nparts; i++)
+					mapping[i] = -1;
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they are same for any two partitioning schemes
+				 * with the same lists. One way to achieve this is to assign
+				 * indexes in the same order as the least values of the
+				 * individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo->null_index = mapping[null_partition_index];
+				else
+					listinfo->null_index = -1;
+
+				/*
+				 * Now assign OIDs from the original array into mapped indexes
+				 * of the result array.  Order of OIDs in the former is defined
+				 * by the catalog scan that retrived them, whereas that in the
+				 * latter is defined by canonicalized representation of list
+				 * values (viz. ordering of list values).
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[mapping[i]] = oids[i];
+
+				result->bounds->listinfo = listinfo;
+				pfree(mapping);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			PartitionBoundList *list;
+
+			Assert(IsA(bound, PartitionBoundList));
+			list = (PartitionBoundList *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, list->location)));
+				}
+			}
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionBoundRange *range;
+
+			Assert(IsA(bound, PartitionBoundRange));
+			range = (PartitionBoundRange *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionBoundList))
+	{
+		PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionBoundRange))
+	{
+		PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionBoundList *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionBoundRange *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *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 *nulltest;
+
+		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->parttypid[0],
+									   key->parttypmod[0],
+									   key->parttypcoll[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->partcollation[0] != key->parttypcoll[0])
+			key_col = (Node *) makeRelabelType((Expr *) key_col,
+											   key->partopcintype[0],
+											   -1,
+											   key->partcollation[0],
+											   COERCE_EXPLICIT_CAST);
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  make_opclause(operoid, BOOLOID,
+										false,
+										(Expr *) key_col,
+										(Expr *) key_val,
+										InvalidOid,
+										key->partcollation[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	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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->partcollation[i]);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull, NULL);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		if (DatumGetBool(test_result))
+		{
+			/*
+			 * Yes, build leftop eq lower_val
+			 */
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+								make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/* Build leftop ge/gt lower_val */
+			strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+										: BTGreaterStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* Build leftop le/lt upper_val */
+			strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+											: BTLessStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* 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->parttypid[col],
+								  key->parttypid[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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										  key->parttypbyval[0],
+										  key->parttyplen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+										  key->parttyplen[i]);
+			i++;
+		}
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+									   key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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->partcollation[i],
+												 val1[i], val2[i]));
+		if (result != 0)
+			break;
+	}
+
+	return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -163,6 +164,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 */
+	List	   *partition_quals; /* partition check quals for attach partition
+								  * validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,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
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
 	 * but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1471,7 +1514,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;
@@ -1516,8 +1560,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),
@@ -1544,6 +1588,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),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,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;
@@ -1856,6 +1932,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)
 	{
@@ -1885,16 +1964,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);
@@ -1931,8 +2014,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 */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2600,11 +2689,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2846,8 +2936,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3126,6 +3219,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);
@@ -3443,6 +3541,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);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3762,6 +3872,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);
@@ -3947,7 +4063,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7892,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);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,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;
 
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,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);
 
@@ -10332,16 +10545,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;
 }
 
 /*
@@ -10392,7 +10597,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
@@ -10410,12 +10615,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10455,8 +10668,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10485,7 +10702,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.
@@ -10504,10 +10721,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10610,13 +10878,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];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,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);
 
@@ -10668,11 +10925,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
@@ -10681,7 +10947,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)))
@@ -10743,7 +11009,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);
 
@@ -10774,7 +11040,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)
@@ -10786,30 +11052,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;
 }
 
 /*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+	PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+	PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+	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
  * ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundList:
+			retval = _copyPartitionBoundList(from);
+			break;
+		case T_PartitionBoundRange:
+			retval = _copyPartitionBoundRange(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 a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,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);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *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
  */
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundList:
+			retval = _equalPartitionBoundList(a, b);
+			break;
+		case T_PartitionBoundRange:
+			retval = _equalPartitionBoundRange(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 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -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);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *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'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundList:
+				_outPartitionBoundList(str, obj);
+				break;
+			case T_PartitionBoundRange:
+				_outPartitionBoundRange(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+	READ_LOCALS(PartitionBoundList);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+	READ_LOCALS(PartitionBoundRange);
+
+	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
@@ -2497,6 +2526,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionBoundList();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionBoundRange();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		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.
@@ -572,7 +580,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
@@ -588,7 +596,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
@@ -602,7 +611,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
 
@@ -2374,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:
@@ -2469,6 +2510,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionBoundList *n = makeNode(PartitionBoundList);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+					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); }
+		;
 
 /*****************************************************************************
  *
@@ -2886,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 OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2973,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;
@@ -2994,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;
@@ -4551,6 +4707,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11156,6 +11354,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;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13843,6 +14044,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 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..f565a5d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +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 */
@@ -130,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);
 
 
 /*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partspec != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,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->partspec)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -359,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);
@@ -898,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;
@@ -1117,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;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,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;
@@ -3026,3 +3059,334 @@ 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_TABLE)
+		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_TABLE)
+		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;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+	PartitionBoundList  *list, *result_list;
+	PartitionBoundRange *range, *result_range;
+
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (!IsA(bound, PartitionBoundList))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionBoundList *) bound;
+			result_list = makeNode(PartitionBoundList);
+
+			foreach(cell, list->values)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\""
+							" of key column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+					 parser_errposition(cxt->pstate, exprLocation(value))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				result_list->values = lappend(result_list->values, value);
+			}
+			return (Node *) result_list;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			char	*colname;
+
+			if (!IsA(bound, PartitionBoundRange))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionBoundRange *) bound;
+
+			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(PartitionBoundRange);
+			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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(cxt->pstate, exprLocation(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)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[0] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+						colname = deparse_expression((Node *) list_nth(partexprs, i),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								colname),
+							 parser_errposition(cxt->pstate, exprLocation(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 e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2609,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 */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..07a8d76
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundList,
+	T_PartitionBoundRange,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1562,7 +1597,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
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 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/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..e0181c4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
 Partition key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- 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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 5a0d933..7255690 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ 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 literal 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 (2);
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a 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

0004-psql-and-pg_dump-support-for-partitions-8.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-8.patchDownload
From 1061da9b2d615560f26cfbebd89cd621b7738ab8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |   98 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 ++++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++++----
 src/test/regress/expected/create_table.out |   39 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 ++++
 7 files changed, 393 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82f03ea..6e2bdde 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundList:
+			{
+				PartitionBoundList *list_spec = (PartitionBoundList *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionBoundRange:
+			{
+				PartitionBoundRange *range_spec = (PartitionBoundRange *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8aa615b..9ec4323 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6243,6 +6243,63 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15423,6 +15480,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15451,8 +15519,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15481,7 +15552,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15546,15 +15618,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15724,6 +15803,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e0181c4..19257d6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7255690..fecae9b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-8.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-8.patchDownload
From 1e84b8e7f1f663d564187947fdcfe4232396f430 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.

Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
 src/backend/optimizer/plan/initsplan.c |   17 ++-
 src/backend/optimizer/prep/prepunion.c |  282 +++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 3 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Do not flatten the inheritance hierarchy if partitioned table, unless
+	 * this is the result relation.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rti != root->parse->resultRelation)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
-		 */
-		if (childOID != parentOID)
-		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
-		}
-
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (oldrc)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-8.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-8.patchDownload
From b376378f7cbb06c0b8c48fbef28788861c19c045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-8.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-8.patchDownload
From 53d6b94dc11abcc1436538baacbc6c74dae66cdf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  209 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 214 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 047249f..e742007 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionDesc() */
 static int32 list_value_cmp(const void *a, const void *b, void *arg);
 static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -754,6 +813,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1411,6 +1517,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 07a8d76..6ed8371 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-8.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-8.patchDownload
From e05d4ef20413ba6c5b19cf84701d04828b2283f9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  343 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  142 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   71 +++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 18 files changed, 942 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e742007..8f4dd54 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * RelationBuildPartitionDesc
@@ -1536,7 +1551,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1620,6 +1635,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -1967,3 +2243,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44c273c..8c7e5f2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1729,6 +1825,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2299,7 +2413,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2455,10 +2572,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2510,6 +2663,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2530,7 +2708,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2560,7 +2747,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2593,7 +2787,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..5e3922f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2082,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1dbfd78..248569d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a507ca..7dd6e10 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e11e670..bc5ad4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..476c40f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,6 +6152,77 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6ed8371..08c38b2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 697c90f..6e51773 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1141,6 +1142,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-8.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-8.patchDownload
From 22b9ce03f6ae31c178b2d48ab97cedadf106d8e4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f43352c..8084029 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#69Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#68)
Re: Declarative partitioning - another take

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Attached revised patches. Also, includes a fix for an issue reported by
Rajkumar Raghuwanshi [1] which turned out to be a bug in one of the later
patches. I will now move on to addressing the comments on patch 0003.

Thanks a lot for the review!

Thanks,
Amit

[1]
/messages/by-id/5dded2f1-c7f6-e7fc-56b
5-23ab59495e4b@lab.ntt.co.jp

Hi,

I have applied latest patches, getting some error and crash, please check
if you are also able to reproduce the same.

Observation1 : Not able to create index on partition table.
--------------
CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);

CREATE INDEX idx_rp_c1 on rp(c1);
ERROR: cannot create index on partitioned table "rp"

Observation2 : Getting cache lookup failed error for multiple column range
partition
--------------
CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));

CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
10);
ERROR: cache lookup failed for attribute 0 of relation 16429

Observation3 : Getting server crash with multiple column range partition
--------------
CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
10);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#70Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#69)
9 attachment(s)
Re: Declarative partitioning - another take

On 2016/10/07 17:33, Rajkumar Raghuwanshi wrote:

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote wrote:
I have applied latest patches, getting some error and crash, please check
if you are also able to reproduce the same.

Observation1 : Not able to create index on partition table.
--------------
CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);

CREATE INDEX idx_rp_c1 on rp(c1);
ERROR: cannot create index on partitioned table "rp"

This one is a new behavior as I mentioned earlier in my previous email.

Observation2 : Getting cache lookup failed error for multiple column range
partition
--------------
CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));

CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
10);
ERROR: cache lookup failed for attribute 0 of relation 16429

Observation3 : Getting server crash with multiple column range partition
--------------
CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
10);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>

Fixed. Should have been caught when running the regression tests after I
made changes to 0001 to address some review comments (apparently there was
no test in 0003 that would've caught this, so added a new one, thanks!).

Attached updated patches.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-9.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-9.patchDownload
From c7390f79a3cf11a6ca2d580b081ea87856954045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  160 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |    9 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  434 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 +++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   11 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  257 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    5 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   36 +++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   66 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   34 +++
 src/test/regress/sql/create_table.sql      |  137 +++++++++
 51 files changed, 1915 insertions(+), 49 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..893c899 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partitioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is routed
+      to a partition based on the value of columns or expressions in the
+      partition key.  If no existing partition matches the values in the new
+      row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition key, opclass info into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references which will count as self-dependency items; in this case,
+	 * the depender is the table itself (there is no such thing as partition
+	 * key object).
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	/* Process and store partition key information, if any */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier. 
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
 	ObjectAddressSet(address, RelationRelationId, relationId);
 
 	/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (attnum == partattno)
+				return true;
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			if (is_expr)
+				*is_expr = true;
+
+			/* Find all attributes referenced */
+			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;
+
+				if (attno == attnum)
+					return true;
+			}
+			partexprs_item = lnext(partexprs_item);
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5840,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5885,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partition strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTEDRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTEDRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
 	DEPENDENCY_INTERNAL = 'i',
 	DEPENDENCY_EXTENSION = 'e',
 	DEPENDENCY_AUTO_EXTENSION = 'x',
-	DEPENDENCY_PIN = 'p'
+	DEPENDENCY_PIN = 'p',
 } DependencyType;
 
 /*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy name ('list', 'range', etc.)
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	List	   *partexprs;		/* partition key expressions, if any */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation */
+	Oid		   *partcollation;
+
+	/* Type information of partition attributes */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 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_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-9.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-9.patchDownload
From f5a8f2425e637d010b83dd68d50611228747a749 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  146 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  134 ++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 366 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..82f03ea 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,150 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 299e887..8aa615b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5235,7 +5240,108 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90600)
+	if (fout->remoteVersion >= 100000)
+	{
+		PQExpBuffer acl_subquery = createPQExpBuffer();
+		PQExpBuffer racl_subquery = createPQExpBuffer();
+		PQExpBuffer initacl_subquery = createPQExpBuffer();
+		PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+		PQExpBuffer attacl_subquery = createPQExpBuffer();
+		PQExpBuffer attracl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 *
+		 * Left join to detect if any privileges are still as-set-at-init, in
+		 * which case we won't dump out ACL commands for those.
+		 */
+
+		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+						initracl_subquery, "c.relacl", "c.relowner",
+				 "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+						dopt->binary_upgrade);
+
+		buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+					  attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+						dopt->binary_upgrade);
+
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "%s AS relacl, %s as rrelacl, "
+						  "%s AS initrelacl, %s as initrrelacl, "
+						  "c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relrowsecurity, c.relforcerowsecurity, "
+						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "tc.relminmxid AS tminmxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "c.relreplident, c.relpages, "
+						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+						  "d.refobjid AS owning_tab, "
+						  "d.refobjsubid AS owning_col, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						  "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+						  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+						  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+						  "tc.reloptions AS toast_reloptions, "
+						  "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = at.attnum)"
+						  "WHERE at.attrelid = c.oid AND ("
+						  "%s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL"
+						  "))"
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+						  "LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = 0) "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  acl_subquery->data,
+						  racl_subquery->data,
+						  initacl_subquery->data,
+						  initracl_subquery->data,
+						  username_subquery,
+						  attacl_subquery->data,
+						  attracl_subquery->data,
+						  attinitacl_subquery->data,
+						  attinitracl_subquery->data,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
+
+		destroyPQExpBuffer(acl_subquery);
+		destroyPQExpBuffer(racl_subquery);
+		destroyPQExpBuffer(initacl_subquery);
+		destroyPQExpBuffer(initracl_subquery);
+
+		destroyPQExpBuffer(attacl_subquery);
+		destroyPQExpBuffer(attracl_subquery);
+		destroyPQExpBuffer(attinitacl_subquery);
+		destroyPQExpBuffer(attinitracl_subquery);
+	}
+	else if (fout->remoteVersion >= 90600)
 	{
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5884,6 +5990,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +6061,10 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+			tblinfo[i].partkeydef = NULL;
+		else
+			tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5998,7 +6109,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6213,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -15456,6 +15572,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15516,6 +15635,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15654,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15751,6 +15872,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-9.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-9.patchDownload
From fb29206c05b8c27763c7ee620bc11836cea56ab8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  101 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   26 +
 doc/src/sgml/ref/create_table.sgml         |   99 ++-
 src/backend/bootstrap/bootparse.y          |    1 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |   33 +-
 src/backend/catalog/index.c                |    3 +-
 src/backend/catalog/partition.c            | 1760 ++++++++++++++++++++++++++++
 src/backend/catalog/toasting.c             |    1 +
 src/backend/commands/cluster.c             |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  774 ++++++++++--
 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                  |  208 ++++-
 src/backend/parser/parse_agg.c             |    1 -
 src/backend/parser/parse_utilcmd.c         |  373 ++++++-
 src/backend/utils/cache/relcache.c         |  105 ++-
 src/include/catalog/heap.h                 |    4 +-
 src/include/catalog/partition.h            |   58 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   42 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  219 ++++
 src/test/regress/expected/create_table.out |  188 +++
 src/test/regress/sql/alter_table.sql       |  192 +++
 src/test/regress/sql/create_table.sql      |  139 +++
 32 files changed, 4387 insertions(+), 160 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..c4061c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,48 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as partition of the target table.  The partition bound specification must
+      correspond to the partitioning method and partitioning key of the target
+      table.  The table to be attached must have all the columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the matching constraints as the target table.
+      That includes both <literal>NOT NULL</literal> and <literal>CHECK</literal>
+      constraints.  If some <literal>CHECK</literal> constraint of the table
+      being attached is marked <literal>NO INHERIT</literal>, the command will
+      fail; such constraints must either be dropped or recreated without the
+      <literal>NO INHERIT</literal> mark.  Currently <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>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +770,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +987,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1045,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 specified.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1120,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP TABLE</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1309,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..f05f212 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES 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 893c899..cb78145 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,14 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | 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 }
+
 <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 +99,8 @@ 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>
 
+</synopsis>
  </refsynopsisdiv>
 
  <refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +251,51 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partitioning key of the parent table, and must not overlap
+      with any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.  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.  One can also specify table
+      constraints, in addition to those inherited from the parent.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping 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>
@@ -1424,7 +1488,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
 													  0,
 													  ONCOMMIT_NOOP,
 													  (Datum) 0,
+													  (Datum) 0,
 													  false,
 													  true,
 													  false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions);
+					Datum reloptions,
+					Datum relpartbound);
 static ObjectAddress AddNewRelationType(const char *typeName,
 				   Oid typeNamespace,
 				   Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions)
+				   Datum reloptions,
+				   Datum relpartbound)
 {
 	Form_pg_class rd_rel = new_rel_desc->rd_rel;
 	Datum		values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
 		values[Anum_pg_class_reloptions - 1] = reloptions;
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
+	if (relpartbound != (Datum) 0)
+		values[Anum_pg_class_relpartbound - 1] = relpartbound;
+	else
+		nulls[Anum_pg_class_relpartbound - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
-					Datum reloptions)
+					Datum reloptions,
+					Datum relpartbound)
 {
 	Form_pg_class new_rel_reltup;
 
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
 	InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
-					   relacl, reloptions);
+					   relacl, reloptions, relpartbound);
 }
 
 
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
-						reloptions);
+						reloptions,
+						relpartbound);
 
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
 						 RelationGetRelationName(rel))));
+	if (is_no_inherit && rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+						 RelationGetRelationName(rel)),
+				 errdetail("Table \"%s\" is a partition.",
+						 RelationGetRelationName(rel))));
 
 	/*
 	 * Create the Check Constraint
@@ -2479,7 +2498,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 && !rel->rd_rel->relispartition)
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
 	InsertPgClassTuple(pg_class, indexRelation,
 					   RelationGetRelid(indexRelation),
 					   (Datum) 0,
-					   reloptions);
+					   reloptions,
+					   (Datum) 0);
 
 	/* done with pg_class */
 	heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..047249f
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1760 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+	struct ListInfo	   *listinfo;
+	struct RangeInfo   *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; maps
+						 * element-by-element with the values array */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+	struct PartitionRange	**ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+	struct PartitionRangeBound	*lower;
+	struct PartitionRangeBound	*upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	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 */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+	int		nvalues;
+	Datum  *values;
+	bool	has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+	Datum	value;
+	int		index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+	Oid				oid;
+	PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundList *list_spec,
+							ListInfo *listinfo,
+							int *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionBoundRange *range_spec,
+							RangeInfo *rangeinfo,
+							int n,
+							int *with);
+
+/* Support get_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_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+							PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+							PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	ListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	RangePartition **range_parts;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		/* Collect bound spec nodes in a list */
+		i = 0;
+		foreach(cell, partoids)
+		{
+			Oid 		partrelid = lfirst_oid(cell);
+			HeapTuple	tuple;
+			Datum		datum;
+			bool		isnull;
+			Node	   *boundspec;
+
+			tuple = SearchSysCache1(RELOID, partrelid);
+			Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+			datum = SysCacheGetAttr(RELOID, tuple,
+									Anum_pg_class_relpartbound,
+									&isnull);
+			Assert(!isnull);
+			boundspec = stringToNode(TextDatumGetCString(datum));
+			boundspecs = lappend(boundspecs, boundspec);
+			ReleaseSysCache(tuple);
+			oids[i++] = partrelid;
+		}
+
+		/* Convert from node to a internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionList  **list;
+				int		j;
+
+				list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+				i = 0;
+				all_values_count = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundList  *list_spec;
+
+					Assert(IsA(lfirst(cell), PartitionBoundList));
+					list_spec = (PartitionBoundList *) lfirst(cell);
+
+					list[i] = make_list_from_spec(key, list_spec);
+
+					if (list[i]->has_null)
+					{
+						found_null_partition = true;
+						null_partition_index = i;
+					}
+					all_values_count += list[i]->nvalues;
+					i++;
+				}
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (ListValue **)
+							  palloc0(all_values_count * sizeof(ListValue *));
+				j = 0;
+				for (i = 0; i < nparts; i++)
+				{
+					int		k;
+
+					for (k = 0; k < list[i]->nvalues; k++)
+					{
+						ListValue	*list_value;
+
+						list_value = (ListValue *) palloc0(sizeof(ListValue));
+						list_value->value = datumCopy(list[i]->values[k],
+													  key->parttypbyval[0],
+													  key->parttyplen[0]);
+						list_value->index = i;
+						all_values[j++] = list_value;
+					}
+				}
+
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				range_parts = (RangePartition **)
+								palloc0(nparts * sizeof(RangePartition *));
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundRange  *range_spec;
+					RangePartition		*range_part;
+
+					Assert(IsA(lfirst(cell), PartitionBoundRange));
+					range_spec = (PartitionBoundRange *) lfirst(cell);
+					range_part = (RangePartition *)
+								palloc0(nparts * sizeof(RangePartition));
+					range_part->oid = oids[i];
+					range_part->range = make_range_from_spec(key, range_spec);
+					range_parts[i++] = range_part;
+				}
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->bounds = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				ListInfo   *listinfo;
+				int		   *mapping;
+				int			next_index = 0;
+
+				listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+				mapping = (int *) palloc(sizeof(int) * nparts);
+
+				/* Initialize with invalid mapping values */
+				for (i = 0; i < nparts; i++)
+					mapping[i] = -1;
+
+				/* Sort so that we can perform binary search over values */
+				qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+							list_value_cmp, (void *) key);
+
+				listinfo->nvalues = all_values_count;
+				listinfo->has_null = found_null_partition;
+				listinfo->values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo->indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they are same for any two partitioning schemes
+				 * with the same lists. One way to achieve this is to assign
+				 * indexes in the same order as the least values of the
+				 * individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo->values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+					listinfo->indexes[i] = all_values[i]->index;
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo->null_index = mapping[null_partition_index];
+				else
+					listinfo->null_index = -1;
+
+				/*
+				 * Now assign OIDs from the original array into mapped indexes
+				 * of the result array.  Order of OIDs in the former is defined
+				 * by the catalog scan that retrived them, whereas that in the
+				 * latter is defined by canonicalized representation of list
+				 * values (viz. ordering of list values).
+				 */
+				for (i = 0; i < nparts; i++)
+					result->oids[mapping[i]] = oids[i];
+
+				result->bounds->listinfo = listinfo;
+				pfree(mapping);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				RangeInfo *rangeinfo;
+
+				rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+				rangeinfo->ranges = (PartitionRange **)
+											palloc0(nparts * sizeof(PartitionRange *));
+
+				/*
+				 * Sort so that we can perform binary search over ranges.
+				 * Note that this will also sort oids together.
+				 */
+				qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+								range_partition_cmp, (void *) key);
+
+				for (i = 0; i < nparts; i++)
+				{
+					result->oids[i] = range_parts[i]->oid;
+					rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+													  key);
+				}
+
+				result->bounds->rangeinfo = rangeinfo;
+				break;
+			}
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2, int n)
+{
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent.  Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, NoLock); /* already locked */
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			PartitionBoundList *list;
+
+			Assert(IsA(bound, PartitionBoundList));
+			list = (PartitionBoundList *) bound;
+
+			if (pdesc->nparts > 0)
+			{
+				ListInfo *listinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->listinfo);
+				listinfo = pdesc->bounds->listinfo;
+				if (list_overlaps_existing_partition(key, list, listinfo,
+													 &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+								relname, get_rel_name(pdesc->oids[with])),
+						 parser_errposition(pstate, list->location)));
+				}
+			}
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionBoundRange *range;
+
+			Assert(IsA(bound, PartitionBoundRange));
+			range = (PartitionBoundRange *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, range->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				RangeInfo *rangeinfo;
+
+				Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+				rangeinfo = pdesc->bounds->rangeinfo;
+				if (range_overlaps_existing_partition(key, range, rangeinfo,
+													  pdesc->nparts, &with))
+				{
+					Assert(with >= 0);
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("partition \"%s\" would overlap partition \"%s\"",
+									relname, get_rel_name(pdesc->oids[with])),
+							 parser_errposition(pstate, range->location)));
+				}
+			}
+			break;
+		}
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf-level partitions of relation with OID
+ *		'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	int			i;
+	ListCell   *partexprs_item;
+
+	Assert(key);
+
+	if (IsA(bound, PartitionBoundList))
+	{
+		PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_LIST);
+		my_qual = get_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionBoundRange))
+	{
+		PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+		Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+		my_qual = get_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	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_qual = (List *) translate_var_attno((Node *) my_qual,
+													   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_qual = (List *) translate_var_attno((Node *) my_qual,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+	return list_values_cmp((PartitionKey) arg,
+						   (*(const ListValue **) a)->value,
+						   (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const RangePartition **) a)->range,
+							   (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionBoundList *list_spec,
+								 ListInfo *listinfo,
+								 int *with)
+{
+	int			i;
+	PartitionList	   *new_list;
+
+	Assert(listinfo);
+	Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	if (new_list->has_null && listinfo->has_null)
+	{
+		*with = listinfo->null_index;
+		return true;
+	}
+
+	for (i = 0; i < new_list->nvalues; i++)
+	{
+		int		found;
+
+		/* bsearch a new list's value in listinfo->values */
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									new_list->values[i],
+									key);
+		if (found >= 0)
+		{
+			*with = listinfo->indexes[found];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange			*range;
+	PartitionRangeBound	*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 infinity 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 overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionBoundRange *range_spec,
+								  RangeInfo *rangeinfo,
+								  int n, int *with)
+{
+	int			i;
+	PartitionRange	   *range;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	Assert(rangeinfo);
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+		{
+			*with = i;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * 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_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand).  If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+	ArrayExpr		   *arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, leave out the test but remove null Const from the list
+	 * and create a IS NULL test to be OR'd with ScalarArrayOpExpr.  The
+	 * latter because null-valued expressions does not have the desired
+	 * behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(list_spec->values); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			list_spec->values = list_delete_cell(list_spec->values,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = list_spec->values;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	return nulltest1 ? list_make2(nulltest1, opexpr)
+					 : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+												list_make2(nulltest2, opexpr),
+												-1))
+								  : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *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 *nulltest;
+
+		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->parttypid[0],
+									   key->parttypmod[0],
+									   key->parttypcoll[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->partcollation[0] != key->parttypcoll[0])
+			key_col = (Node *) makeRelabelType((Expr *) key_col,
+											   key->partopcintype[0],
+											   -1,
+											   key->partcollation[0],
+											   COERCE_EXPLICIT_CAST);
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+
+		/* Build the opexpr and return the list containing it and nulltest */
+		return list_make2(nulltest,
+						  make_opclause(operoid, BOOLOID,
+										false,
+										(Expr *) key_col,
+										(Expr *) key_val,
+										InvalidOid,
+										key->partcollation[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	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * 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->partcollation[i]);
+		fix_opfuncids((Node *) test_expr);
+		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_result = ExecEvalExprSwitchContext(test_exprstate,
+												GetPerTupleExprContext(estate),
+												&isNull, NULL);
+		MemoryContextSwitchTo(oldcxt);
+		FreeExecutorState(estate);
+
+		if (DatumGetBool(test_result))
+		{
+			/*
+			 * Yes, build leftop eq lower_val
+			 */
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+								make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/* Build leftop ge/gt lower_val */
+			strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+										: BTGreaterStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* Build leftop le/lt upper_val */
+			strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+											: BTLessStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+
+			/* 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->parttypid[col],
+								  key->parttypid[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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Generate from pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/* Turn that bound into a list of equivalent check quals */
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+	PartitionList *list;
+	ListCell   *cell;
+	int			i,
+				num_non_null;
+
+
+	list = (PartitionList *) palloc0(sizeof(PartitionList));
+	list->has_null = false;
+
+	/* Never put a null into the values array, flag instead */
+	num_non_null = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->has_null = true;
+		else
+			num_non_null++;
+	}
+
+	list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (!val->constisnull)
+			list->values[i++] = datumCopy(val->constvalue,
+										  key->parttypbyval[0],
+										  key->parttyplen[0]);
+	}
+
+	list->nvalues = num_non_null;
+
+	return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+					PartitionKey key)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+	PartitionRange *range;
+
+	range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	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;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+										  key->parttyplen[i]);
+			i++;
+		}
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+	PartitionRange *result;
+
+	result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	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->parttypbyval[i],
+									   key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+	int		i;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < n; i++)
+	{
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->lower,
+									  r2->ranges[i]->lower) != 0)
+			return false;
+
+		if (partition_range_bound_cmp(key,
+									  r1->ranges[i]->upper,
+									  r2->ranges[i]->upper) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *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;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *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->partcollation[i],
+												 val1[i], val2[i]));
+		if (result != 0)
+			break;
+	}
+
+	return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   0,
 										   ONCOMMIT_NOOP,
 										   reloptions,
+										   (Datum) 0,
 										   false,
 										   true,
 										   true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  0,
 										  ONCOMMIT_NOOP,
 										  reloptions,
+										  (Datum) 0,
 										  false,
 										  true,
 										  true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -163,6 +164,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 */
+	List	   *partition_quals; /* partition check quals for attach partition
+								  * validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
+	Datum		relpartbound;
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,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
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		}
 	}
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		char   *boundString;
+		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(relname, parentId, stmt->partbound);
+		boundString = nodeToString(stmt->partbound);
+		relpartbound = CStringGetTextDatum(boundString);
+	}
+	else
+		relpartbound = (Datum) 0;
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  parentOidCount,
 										  stmt->oncommit,
 										  reloptions,
+										  relpartbound,
 										  true,
 										  allowSystemTableMods,
 										  false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
+	if (classform->relispartition)
+		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.")));
+
 	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
 	 * but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1471,7 +1514,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;
@@ -1516,8 +1560,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),
@@ -1544,6 +1588,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),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,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;
@@ -1856,6 +1932,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)
 	{
@@ -1885,16 +1964,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);
@@ -1931,8 +2014,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 */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (classform->relispartition && !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
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2600,11 +2689,12 @@ 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 ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2846,8 +2936,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,
+				 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3126,6 +3219,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);
@@ -3443,6 +3541,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);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3762,6 +3872,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);
@@ -3947,7 +4063,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("source table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if we shouldn't be dropping the
+	 * NOT NULL because it is present in the parent.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				/*
 				 * If the child column has other definition sources, just
 				 * decrement its inheritance count; if not, recurse to delete
-				 * it.
+				 * it. If the child table is partition, remain in sync with
+				 * the parent.
 				 */
-				if (childatt->attinhcount == 1 && !childatt->attislocal)
+				if (childatt->attinhcount == 1 &&
+					(!childatt->attislocal || childrel->rd_rel->relispartition))
 				{
 					/* Time to delete this child column, too */
 					ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ 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 &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		return address;
 
 	/*
@@ -7892,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);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 			/*
 			 * If the child constraint has other definition sources, just
 			 * decrement its inheritance count; if not, recurse to delete it.
+			 * If the child table is partition, remain in sync with the parent.
 			 */
-			if (con->coninhcount == 1 && !con->conislocal)
+			if (con->coninhcount == 1 &&
+				(!con->conislocal || childrel->rd_rel->relispartition))
 			{
 				/* Time to delete this child constraint, too */
 				ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (rel->rd_rel->relispartition && !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))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,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;
 
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,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);
 
@@ -10332,16 +10545,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;
 }
 
 /*
@@ -10392,7 +10597,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
@@ -10410,12 +10615,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ 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(is_attach_partition
+								? "source table \"%s\" has different type for column \"%s\""
+								: "child table \"%s\" has different type for column \"%s\"",
 								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(is_attach_partition
+								? "source table \"%s\" has different collation for column \"%s\""
+								: "source table \"%s\" has different collation for column \"%s\"",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10455,8 +10668,10 @@ 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(is_attach_partition
+								? "column \"%s\" in source table must be marked NOT NULL"
+								: "column \"%s\" in child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing column \"%s\""
+							: "child table is missing column \"%s\"",
 							attributeName)));
 		}
 	}
@@ -10485,7 +10702,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.
@@ -10504,10 +10721,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ 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(is_attach_partition
+								? "source table \"%s\" has different definition for check constraint \"%s\""
+								: "child table \"%s\" has different definition for check constraint \"%s\"",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg(is_attach_partition
+								? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+								: "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
 								NameStr(child_con->conname),
 								RelationGetRelationName(child_rel))));
 
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg(is_attach_partition
+							? "source table is missing constraint \"%s\""
+							: "child table is missing constraint \"%s\"",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10610,13 +10878,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];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,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);
 
@@ -10668,11 +10925,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
@@ -10681,7 +10947,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)))
@@ -10743,7 +11009,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);
 
@@ -10774,7 +11040,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)
@@ -10786,30 +11052,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;
 }
 
 /*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRel,
+				classRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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 that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/* 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+	PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+	PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+	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
  * ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundList:
+			retval = _copyPartitionBoundList(from);
+			break;
+		case T_PartitionBoundRange:
+			retval = _copyPartitionBoundRange(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 a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,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);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *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
  */
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundList:
+			retval = _equalPartitionBoundList(a, b);
+			break;
+		case T_PartitionBoundRange:
+			retval = _equalPartitionBoundRange(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 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -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);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *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'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundList:
+				_outPartitionBoundList(str, obj);
+				break;
+			case T_PartitionBoundRange:
+				_outPartitionBoundRange(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+	READ_LOCALS(PartitionBoundList);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+	READ_LOCALS(PartitionBoundRange);
+
+	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
@@ -2497,6 +2526,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionBoundList();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionBoundRange();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		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.
@@ -572,7 +580,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
@@ -588,7 +596,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
@@ -602,7 +611,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
 
@@ -2374,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:
@@ -2469,6 +2510,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionBoundList *n = makeNode(PartitionBoundList);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+					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); }
+		;
 
 /*****************************************************************************
  *
@@ -2886,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 OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2973,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;
@@ -2994,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;
@@ -4551,6 +4707,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11156,6 +11354,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;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13843,6 +14044,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 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4e14886 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +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 */
@@ -130,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);
 
 
 /*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partspec != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,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->partspec)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -359,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);
@@ -898,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;
@@ -1117,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;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,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;
@@ -3026,3 +3059,341 @@ 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_TABLE)
+		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_TABLE)
+		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)
+{
+	ListCell	   *cell;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+	PartitionBoundList  *list, *result_list;
+	PartitionBoundRange *range, *result_range;
+
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (!IsA(bound, PartitionBoundList))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionBoundList *) bound;
+			result_list = makeNode(PartitionBoundList);
+
+			foreach(cell, list->values)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\""
+							" of key column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+					 parser_errposition(cxt->pstate, exprLocation(value))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				result_list->values = lappend(result_list->values, value);
+			}
+			return (Node *) result_list;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			int		i,
+					j;
+			char   *colname;
+
+			if (!IsA(bound, PartitionBoundRange))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionBoundRange *) bound;
+
+			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(PartitionBoundRange);
+			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 = j = 0;
+				foreach (cell, range->lower)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[i] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+					{
+						colname = deparse_expression((Node *) list_nth(partexprs, j),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+						++j;
+					}
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(cxt->pstate, exprLocation(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 = j = 0;
+				foreach (cell, range->upper)
+				{
+					A_Const	   *con = (A_Const *) lfirst(cell);
+					Node	   *value;
+
+					/* Get the column's name in case we need to output an error */
+					if (key->partattrs[i] != 0)
+						colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+					else
+					{
+						colname = deparse_expression((Node *) list_nth(partexprs, j),
+									deparse_context_for(RelationGetRelationName(parent),
+													 RelationGetRelid(parent)),
+													 false, false);
+						++j;
+					}
+
+					value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+					value = coerce_to_target_type(cxt->pstate,
+												value, exprType(value),
+											get_partition_col_typid(key, i),
+											get_partition_col_typmod(key, i),
+												COERCION_ASSIGNMENT,
+												COERCE_IMPLICIT_CAST,
+												-1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type"
+									" \"%s\" of key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								colname),
+							 parser_errposition(cxt->pstate, exprLocation(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 e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->bounds != NULL)
+		{
+			if (pdesc2->bounds == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+										pdesc1->nparts))
+				return false;
+		}
+		else if (pdesc2->bounds != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2609,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 */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 int oidinhcount,
 						 OnCommitAction oncommit,
 						 Datum reloptions,
+						 Datum relpartbound,
 						 bool use_user_acl,
 						 bool allow_system_table_mods,
 						 bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
 				   Relation new_rel_desc,
 				   Oid new_rel_oid,
 				   Datum relacl,
-				   Datum reloptions);
+				   Datum reloptions,
+				   Datum relpartbound);
 
 extern List *AddRelationNewConstraints(Relation rel,
 						  List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..07a8d76
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		bounds;		/* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundList,
+	T_PartitionBoundRange,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1562,7 +1597,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
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 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/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check 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;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR:  constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ t          |           1
+ t          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ t          |           1
+(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:  partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR:  source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  source table contains a row violating partition bound specification
+-- the check will be skipped, 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;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR:  source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- 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_4;
+ERROR:  relation "part_4" 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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 marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..580a872 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
 Partition key: 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 literal 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 (2);
+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:  partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_bc_part" would overlap partition "ab_part"
+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:  cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with 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:  partition "fail_unb_2" would overlap partition "part_unb_1"
+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:  partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a varchar,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+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:  partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+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:  partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0)
+) 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)
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal 
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR:  cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL:  Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a 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 table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 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 lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, 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);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check 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;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check 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');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, 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;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- 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_4;
+
+-- 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_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::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;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 5a0d933..2610222 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ 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 literal 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 (2);
+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);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a varchar,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+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)
+) 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;
+
+-- specify a column option overriding parent's and a table constraint that will be 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 FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+	CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a 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

0004-psql-and-pg_dump-support-for-partitions-9.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-9.patchDownload
From e12dd4bfd410c9c4d34bfe18428a0064783bbdf9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 +++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  105 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++---
 src/test/regress/expected/create_table.out |   39 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 400 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82f03ea..6e2bdde 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,84 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundList:
+			{
+				PartitionBoundList *list_spec = (PartitionBoundList *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " IN (");
+				sep = "";
+				foreach (cell, list_spec->values)
+				{
+					Const *val = lfirst(cell);
+
+					appendStringInfoString(buf, sep);
+					get_const_expr(val, context, -1);
+					sep = ", ";
+				}
+
+				appendStringInfoString(buf, ")");
+			}
+			break;
+
+		case T_PartitionBoundRange:
+			{
+				PartitionBoundRange *range_spec = (PartitionBoundRange *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				appendStringInfoString(buf, "FOR VALUES");
+
+				appendStringInfoString(buf, " START");
+				if (!range_spec->lower)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->lower)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (!range_spec->lowerinc)
+						appendStringInfoString(buf, " EXCLUSIVE");
+				}
+
+				appendStringInfoString(buf, " END");
+
+				if (!range_spec->upper)
+					appendStringInfoString(buf, " UNBOUNDED");
+				else
+				{
+					appendStringInfoString(buf, " (");
+
+					sep = "";
+					foreach (cell, range_spec->upper)
+					{
+						Const *val = lfirst(cell);
+
+						appendStringInfoString(buf, sep);
+						get_const_expr(val, context, -1);
+						sep = ", ";
+					}
+					appendStringInfoString(buf, ")");
+
+					if (range_spec->upperinc)
+						appendStringInfoString(buf, " INCLUSIVE");
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8aa615b..8ece47a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6243,6 +6243,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15423,6 +15487,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -15451,8 +15526,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -15481,7 +15559,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -15546,15 +15625,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15724,6 +15810,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 580a872..0578389 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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 bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+    "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2610222..3a5301e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Refactor-optimizer-s-inheritance-set-expansion-code-9.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-9.patchDownload
From dbf31707e52176b62af400611cf725d2e9a40053 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.

Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
 src/backend/optimizer/plan/initsplan.c |   17 ++-
 src/backend/optimizer/prep/prepunion.c |  282 +++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 3 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Do not flatten the inheritance hierarchy if partitioned table, unless
+	 * this is the result relation.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rti != root->parse->resultRelation)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
-		 */
-		if (childOID != parentOID)
-		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
-		}
-
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (oldrc)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1

0006-Teach-a-few-places-to-use-partition-check-quals-9.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-9.patchDownload
From 5906075d3fb34b94da3acced9193114849584b76 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5790edc..5b0e8cf 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0007-Introduce-a-PartitionTreeNode-data-structure-9.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-9.patchDownload
From 27e79bc1e907ef78d38bafc92d3379be050b7447 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  209 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 214 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 047249f..e742007 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
 	PartitionRange *range;
 } RangePartition;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 /* Support RelationBuildPartitionDesc() */
 static int32 list_value_cmp(const void *a, const void *b, void *arg);
 static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 /* Support RelationGetPartitionQual() */
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -754,6 +813,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1411,6 +1517,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 07a8d76..6ed8371 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0008-Tuple-routing-for-partitioned-tables-9.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-9.patchDownload
From a32262dacb924742e9bba1ca8f8c0840e39bac9e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  343 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 ++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 ++++-
 src/backend/executor/nodeModifyTable.c  |  142 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   71 +++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 18 files changed, 942 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e742007..8f4dd54 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static PartitionList *make_list_from_spec(PartitionKey key,
 							PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
 							PartitionRangeBound *b2);
 static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
 static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
 
 /*
  * RelationBuildPartitionDesc
@@ -1536,7 +1551,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1620,6 +1635,267 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	ListInfo   *listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->listinfo != NULL);
+	listinfo = pdesc->bounds->listinfo;
+
+	if (isnull && listinfo->has_null)
+		return listinfo->null_index;
+	else if (!isnull)
+	{
+		found = bsearch_list_values(listinfo->values,
+									listinfo->nvalues,
+									value,
+									key);
+		if (found >= 0)
+			return listinfo->indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->bounds->rangeinfo != NULL);
+
+	return bsearch_ranges(key, pdesc->nparts,
+						  pdesc->bounds->rangeinfo, tuple);
+}
+
 /* List partition related support functions */
 
 /*
@@ -1967,3 +2243,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
 
 	return result;
 }
+
+/*
+ * bsearch_ranges
+ *		Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+	int		low, high;
+
+	/* Good ol' bsearch */
+	low = 0;
+	high = n - 1;
+	while (low <= high)
+	{
+		int		idx = (low + high) / 2;
+
+		if (rangeinfo->ranges[idx]->upper->infinite)
+		{
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			break;
+		}
+		else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+		{
+			if (rangeinfo->ranges[idx]->lower->infinite)
+				return idx;
+
+			if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+				return idx;
+
+			high = idx - 1;
+			continue;
+		}
+
+		low = idx + 1;
+	}
+
+	return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound)
+{
+	int32	cmpval = partition_range_tuple_cmp(key, tuple, bound->val);
+
+	if (!cmpval)
+		return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+	return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44c273c..8c7e5f2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1729,6 +1825,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2299,7 +2413,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2455,10 +2572,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2510,6 +2663,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2530,7 +2708,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2560,7 +2747,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2593,7 +2787,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5b0e8cf..5e3922f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1957,6 +2082,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1dbfd78..248569d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a507ca..7dd6e10 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e11e670..bc5ad4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..476c40f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,6 +6152,77 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6ed8371..08c38b2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..93a9cf3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 697c90f..6e51773 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1141,6 +1142,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-9.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-9.patchDownload
From 4a21d65cd3fcc6b759ae93e8c3150cfcaa292ce0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f43352c..8084029 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2930,7 +2898,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2961,12 +2929,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3135,22 +2985,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3387,9 +3163,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3413,18 +3189,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#71Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#70)
Re: Declarative partitioning - another take

It's allowed to specify an non-default opclass in partition by clause,
but I do not see any testcase testing the same. Can you please add
one.

On Fri, Oct 7, 2016 at 2:50 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/07 17:33, Rajkumar Raghuwanshi wrote:

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote wrote:
I have applied latest patches, getting some error and crash, please check
if you are also able to reproduce the same.

Observation1 : Not able to create index on partition table.
--------------
CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);

CREATE INDEX idx_rp_c1 on rp(c1);
ERROR: cannot create index on partitioned table "rp"

This one is a new behavior as I mentioned earlier in my previous email.

Observation2 : Getting cache lookup failed error for multiple column range
partition
--------------
CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));

CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
10);
ERROR: cache lookup failed for attribute 0 of relation 16429

Observation3 : Getting server crash with multiple column range partition
--------------
CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
10);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>

Fixed. Should have been caught when running the regression tests after I
made changes to 0001 to address some review comments (apparently there was
no test in 0003 that would've caught this, so added a new one, thanks!).

Attached updated patches.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#72Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#71)
Re: Declarative partitioning - another take

On 2016/10/07 18:27, Ashutosh Bapat wrote:

It's allowed to specify an non-default opclass in partition by clause,
but I do not see any testcase testing the same. Can you please add
one.

OK, I will add some tests related to that.

Thanks,
Amit

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

#73Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Langote (#68)
Re: Declarative partitioning - another take

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/05 2:12, Robert Haas wrote:

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.

I agree. So we must prevent from the get-go the creation of following
objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):

* Indexes
* Row triggers (?)

Hmm, do we ever fire triggers on the parent for operations on a child
table? Note this thread, which seems possibly relevant:

/messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com

The answer to your question is no.

The thread you quoted discusses statement-level triggers and the
conclusion is that they don't work as desired for UPDATE and DELETE on
inheritance tables. As things stand, only UPDATE or DELETE on the parent
affects the child tables and it's proposed there that the statement-level
triggers on the parent and also on any child tables affected should be
fired in that case.

Doesn't that imply that the statement level triggers should be fired
for all the tables that get changed for statement? If so, then in
your case it should never fire for parent table, which means we could
disallow statement level triggers as well on parent tables?

Some of the other things that we might want to consider disallowing on
parent table could be:
a. Policy on table_name
b. Alter table has many clauses, are all of those allowed and will it
make sense to allow them?

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#74Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Langote (#68)
Re: Declarative partitioning - another take

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/05 2:12, Robert Haas wrote:

Attached revised patches.

Few assorted review comments for 0001-Catalog*:

1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}

Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?

2.
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));

/cannot list/cannot use list

3.
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;

Why is this change required?

4.
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *  definition of the system "partitioned table" relation
+ *  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group

Copyright year should be 2016.

5.
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)

etc. in above comment seems to be unnecessary.

6.
+ {PartitionedRelationId, /* PARTEDRELID */

Here PARTEDRELID sounds inconvenient, how about PARTRELID?

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#75Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Kapila (#74)
Re: Declarative partitioning - another take

Thanks for the review!

On 2016/10/25 20:32, Amit Kapila wrote:

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/05 2:12, Robert Haas wrote:

Attached revised patches.

Few assorted review comments for 0001-Catalog*:

1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}

Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?

Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.

2.
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));

/cannot list/cannot use list

Actually "list partition" works here as a verb, as in "to list partition".

3.
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
Why is this change required?

Looks like a leftover hunk from previous revision. Will fix.

4.
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *  definition of the system "partitioned table" relation
+ *  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group

Copyright year should be 2016.

Oops, will fix.

5.
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)

etc. in above comment seems to be unnecessary.

Will fix, although I thought that list is yet incomplete.

6.
+ {PartitionedRelationId, /* PARTEDRELID */

Here PARTEDRELID sounds inconvenient, how about PARTRELID?

Agreed. There used to be another catalog which had used up PARTRELID, but
that's no longer an issue.

I will include these changes in the next version of patches I will post
soon in reply to [1]/messages/by-id/CA+TgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg@mail.gmail.com
/messages/by-id/CA+TgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg@mail.gmail.com

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

#76Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Kapila (#73)
Re: Declarative partitioning - another take

On 2016/10/25 15:58, Amit Kapila wrote:

On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/05 2:12, Robert Haas wrote:

Hmm, do we ever fire triggers on the parent for operations on a child
table? Note this thread, which seems possibly relevant:

/messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com

The answer to your question is no.

The thread you quoted discusses statement-level triggers and the
conclusion is that they don't work as desired for UPDATE and DELETE on
inheritance tables. As things stand, only UPDATE or DELETE on the parent
affects the child tables and it's proposed there that the statement-level
triggers on the parent and also on any child tables affected should be
fired in that case.

Doesn't that imply that the statement level triggers should be fired
for all the tables that get changed for statement? If so, then in
your case it should never fire for parent table, which means we could
disallow statement level triggers as well on parent tables?

I may have misunderstood statement-level triggers, but don't they apply to
tables *specified* as the target table in the statement, instead of those
*changed* by resulting actions?

Now in case of inheritance, unless ONLY is specified, all tables in the
hierarchy including the parent are *implicitly* specified to be affected
by an UPDATE or DELETE operation. So, if some or all of those tables have
any statement-level triggers defined, they should get fired. That was the
conclusion of that thread, but that TODO item still remains [1].

I am not (or no longer) sure how that argument affects INSERT on
partitioned tables with tuple-routing though. Are partitions at all
levels *implicitly specified to be affected* when we say INSERT INTO
root_partitioned_table?

Some of the other things that we might want to consider disallowing on
parent table could be:
a. Policy on table_name

Perhaps. Since there are no rows in the parent table(s) itself of a
partition hierarchy, it might not make sense to continue to allow creating
row-level security policies on them.

b. Alter table has many clauses, are all of those allowed and will it
make sense to allow them?

Currently, we only disallow the following with partitioned parent tables
as far as alter table is concerned.

- cannot change inheritance by ALTER TABLE partitioned_table INHERIT ...

- cannot let them be regular inheritance parents either - that is, the
following is disallowed: ALTER TABLE some_able INHERIT partitioned_table

- cannot create UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints

- cannot drop column involved in the partitioning key

Most other forms that affect attributes and constraints follow the regular
inheritance behavior (recursion) with certain exceptions such as:

- cannot add/drop an attribute or check constraint to *only* to/from
the parent

- cannot add/drop NOT NULL constraint to/from *only* the parent

Thoughts?

Thanks,
Amit

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

#77Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Langote (#75)
Re: Declarative partitioning - another take

On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}

Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?

Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.

Okay, I don't want to add anything to your existing work unless it is
important. However, I think there should be some agreement on which
of the restrictions are okay for first version of patch. This can
avoid such questions in future from other reviewers.

2.
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));

/cannot list/cannot use list

Actually "list partition" works here as a verb, as in "to list partition".

I am not an expert of this matter, so probably some one having better
grip can comment. Are we using something similar in any other error
message?

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#78Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Kapila (#77)
Re: Declarative partitioning - another take

On 2016/10/26 11:41, Amit Kapila wrote:

On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}

Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?

Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.

Okay, I don't want to add anything to your existing work unless it is
important. However, I think there should be some agreement on which
of the restrictions are okay for first version of patch. This can
avoid such questions in future from other reviewers.

OK, so I assume you don't find this particular restriction problematic in
long term.

2.
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));

/cannot list/cannot use list

Actually "list partition" works here as a verb, as in "to list partition".

I am not an expert of this matter, so probably some one having better
grip can comment. Are we using something similar in any other error
message?

In fact, I changed to the current text after Robert suggested the same [1]/messages/by-id/CA+TgmoaPxXJ14eDVia514UiuQAXyZGqfbz8Qg3G4a8Rz2gKF7w@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoaPxXJ14eDVia514UiuQAXyZGqfbz8Qg3G4a8Rz2gKF7w@mail.gmail.com
/messages/by-id/CA+TgmoaPxXJ14eDVia514UiuQAXyZGqfbz8Qg3G4a8Rz2gKF7w@mail.gmail.com

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

#79Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Langote (#78)
Re: Declarative partitioning - another take

On Wed, Oct 26, 2016 at 8:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/26 11:41, Amit Kapila wrote:

On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}

Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?

Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.

Okay, I don't want to add anything to your existing work unless it is
important. However, I think there should be some agreement on which
of the restrictions are okay for first version of patch. This can
avoid such questions in future from other reviewers.

OK, so I assume you don't find this particular restriction problematic in
long term.

I think you can keep it as you have in patch. After posting your
updated patches, please do send a list of restrictions which this
patch is imposing based on the argument that for first version they
are not essential.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#80Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Kapila (#79)
Re: Declarative partitioning - another take

On 2016/10/26 12:09, Amit Kapila wrote:

On Wed, Oct 26, 2016 at 8:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/26 11:41, Amit Kapila wrote:

On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.

Okay, I don't want to add anything to your existing work unless it is
important. However, I think there should be some agreement on which
of the restrictions are okay for first version of patch. This can
avoid such questions in future from other reviewers.

OK, so I assume you don't find this particular restriction problematic in
long term.

I think you can keep it as you have in patch. After posting your
updated patches, please do send a list of restrictions which this
patch is imposing based on the argument that for first version they
are not essential.

OK, agreed that it will be better to have all such restrictions and
limitations of the first version listed in one place, rather than being
scattered across different emails where they might have been mentioned and
discussed.

I will try to include such a list when posting the latest set of patches.

Thanks,
Amit

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

#81Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#64)
Re: Declarative partitioning - another take

On Wed, Oct 5, 2016 at 7:20 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ latest patch set ]

Reviewing 0003:

5. I wonder how well this handles multi-column partition keys. You've
just got one Datum flag and one is-finite flag per partition, but I
wonder if you don't need to track is-finite on a per-column basis, so
that you could partition on (a, b) and have the first partition go up
to (10, 10), the second to (10, infinity), the third to (20, 10), the
fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
Oracle supports this sort of thing, so perhaps we should, too. On a
related note, I'm not sure it's going to work to treat a composite
partition key as a record type. The user may want to specify a
separate opfamily and collation for each column, not just inherit
whatever the record behavior is. I'm not sure if that's what you are
doing, but the relcache structures don't seem adapted to storing one
Datum per partitioning column per partition, but rather just one Datum
per partition, and I'm not sure that's going to work very well.

@@ -123,6 +123,9 @@ typedef struct RelationData
{
..
  MemoryContext rd_partkeycxt; /* private memory cxt for the below */
  struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List   *rd_partcheck; /* partition CHECK quals */
..
}

I think one thing to consider here is the increase in size of relcache
due to PartitionDescData. I think it will be quite useful in some of
the cases like tuple routing. Isn't it feasible to get it in some
other way, may be by using relpartbound from pg_class tuple?

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#82Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Kapila (#81)
Re: Declarative partitioning - another take

On 2016/10/26 17:57, Amit Kapila wrote:

@@ -123,6 +123,9 @@ typedef struct RelationData
{
..
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List   *rd_partcheck; /* partition CHECK quals */
..
}

I think one thing to consider here is the increase in size of relcache
due to PartitionDescData. I think it will be quite useful in some of
the cases like tuple routing. Isn't it feasible to get it in some
other way, may be by using relpartbound from pg_class tuple?

Whereas pg_class.relpartbound stores partition bound of the *individual
partitions* in Node form, the above relcache struct is associated with
parent tables; it contains some efficient to use (and fairly compact)
representation of bounds of *all* the partitions of the parent. Consider
for example, an array of sorted range bounds for range partitioned tables.

Thanks,
Amit

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

#83Amit Kapila
amit.kapila16@gmail.com
In reply to: Amit Langote (#82)
Re: Declarative partitioning - another take

On Wed, Oct 26, 2016 at 3:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/26 17:57, Amit Kapila wrote:

@@ -123,6 +123,9 @@ typedef struct RelationData
{
..
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List   *rd_partcheck; /* partition CHECK quals */
..
}

I think one thing to consider here is the increase in size of relcache
due to PartitionDescData. I think it will be quite useful in some of
the cases like tuple routing. Isn't it feasible to get it in some
other way, may be by using relpartbound from pg_class tuple?

Whereas pg_class.relpartbound stores partition bound of the *individual
partitions* in Node form, the above relcache struct is associated with
parent tables; it contains some efficient to use (and fairly compact)
representation of bounds of *all* the partitions of the parent.

Okay, but still it will be proportional to number of partitions and
the partition keys. Is it feasible to store ranges only for
partitions that are actively accessed? For example, consider a table
with 100 partitions and the first access to table requires to access
5th partition, then we store ranges for first five partitions or
something like that. This could be helpful, if we consider cases that
active partitions are much less as compare to total partitions of a
table.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

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

#84Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#83)
Re: Declarative partitioning - another take

On Wed, Oct 26, 2016 at 6:12 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

On Wed, Oct 26, 2016 at 3:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/10/26 17:57, Amit Kapila wrote:

@@ -123,6 +123,9 @@ typedef struct RelationData
{
..
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List   *rd_partcheck; /* partition CHECK quals */
..
}

I think one thing to consider here is the increase in size of relcache
due to PartitionDescData. I think it will be quite useful in some of
the cases like tuple routing. Isn't it feasible to get it in some
other way, may be by using relpartbound from pg_class tuple?

Whereas pg_class.relpartbound stores partition bound of the *individual
partitions* in Node form, the above relcache struct is associated with
parent tables; it contains some efficient to use (and fairly compact)
representation of bounds of *all* the partitions of the parent.

Okay, but still it will be proportional to number of partitions and
the partition keys. Is it feasible to store ranges only for
partitions that are actively accessed? For example, consider a table
with 100 partitions and the first access to table requires to access
5th partition, then we store ranges for first five partitions or
something like that. This could be helpful, if we consider cases that
active partitions are much less as compare to total partitions of a
table.

I have serious doubt about whether it's a good idea to do that EVER,
but it certainly doesn't need to be in the first version of this
patch.

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

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

#85Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#73)
Re: Declarative partitioning - another take

On Tue, Oct 25, 2016 at 2:58 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

a. Policy on table_name

No, because queries against the parent will apply the policy to
children. See today's commit
162477a63d3c0fd1c31197717140a88077a8d0aa.

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

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

#86Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#75)
Re: Declarative partitioning - another take

On Tue, Oct 25, 2016 at 9:06 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I will include these changes in the next version of patches I will post
soon in reply to [1].
[1]
/messages/by-id/CA+TgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg@mail.gmail.com

How soon? Tempus fugit, and tempus in this release cycle fugit
particularly quickly. It looks like the last revision of these
patches was on October 7th, and that's now more than 2 weeks ago. We
need to get moving here if this is going to happen this cycle.

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

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

#87Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#76)
Re: Declarative partitioning - another take

On Tue, Oct 25, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I am not (or no longer) sure how that argument affects INSERT on
partitioned tables with tuple-routing though. Are partitions at all
levels *implicitly specified to be affected* when we say INSERT INTO
root_partitioned_table?

I'd say yes.

Some of the other things that we might want to consider disallowing on
parent table could be:
a. Policy on table_name

Perhaps. Since there are no rows in the parent table(s) itself of a
partition hierarchy, it might not make sense to continue to allow creating
row-level security policies on them.

No, per my previous email. Those policies are emphatically not without effect.

b. Alter table has many clauses, are all of those allowed and will it
make sense to allow them?

Currently, we only disallow the following with partitioned parent tables
as far as alter table is concerned.

- cannot change inheritance by ALTER TABLE partitioned_table INHERIT ...

- cannot let them be regular inheritance parents either - that is, the
following is disallowed: ALTER TABLE some_able INHERIT partitioned_table

- cannot create UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints

- cannot drop column involved in the partitioning key

Most other forms that affect attributes and constraints follow the regular
inheritance behavior (recursion) with certain exceptions such as:

- cannot add/drop an attribute or check constraint to *only* to/from
the parent

- cannot add/drop NOT NULL constraint to/from *only* the parent

Thoughts?

Seems sensible to me.

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

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

#88Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#86)
Re: Declarative partitioning - another take

On 2016/10/27 3:13, Robert Haas wrote:

On Tue, Oct 25, 2016 at 9:06 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I will include these changes in the next version of patches I will post
soon in reply to [1].
[1]
/messages/by-id/CA+TgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg@mail.gmail.com

How soon? Tempus fugit, and tempus in this release cycle fugit
particularly quickly. It looks like the last revision of these
patches was on October 7th, and that's now more than 2 weeks ago. We
need to get moving here if this is going to happen this cycle.

Sorry about the delay, I'll post no later than tomorrow.

Thanks,
Amit

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

#89Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#64)
9 attachment(s)
Re: Declarative partitioning - another take

On 2016/10/05 10:50, Robert Haas wrote:

On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ latest patch set ]

Reviewing 0003:

Thanks a lot for the review and sorry about the delay in replying.

+ This form attaches an existing table (partitioned or otherwise) as

(which might itself be partitioned)

+      partition of the target table.  Partition bound specification must
+      correspond with the partition method and the key of the target table.

The partition bound specification must correspond to the partitioning
method and partitioning key of the target table.

+      The table being attached must have all the columns of the target table
+      with matching types and no more. Also, it must have all the matching

The table to be attached must have all of the same columns as the
target table and no more; moreover, the column types must also match.

Done.

+      with matching types and no more. Also, it must have all the matching
+      constraints as the target table.  That includes both <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  If some <literal>CHECK</literal>
+      constraint of the table being attached is marked <literal>NO
INHERIT</literal>,
+      the command will fail; such constraints must either be dropped or
+      recreated without the <literal>NO INHERIT</literal> mark.

Why all of these requirements?

Except the last one, regular inheritance currently has the same
requirements; from the docs:

"""
To be added as a child, the target table must already contain all the same
columns as the parent (it could have additional columns, too). The columns
must have matching data types, and if they have NOT NULL constraints in
the parent then they must also have NOT NULL constraints in the child.

There must also be matching child-table constraints for all CHECK
constraints of the parent, except those marked non-inheritable (that is,
created with ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT) in the parent,
which are ignored; all child-table constraints matched must not be marked
non-inheritable.
"""

Note that the "(it could have additional columns, too)" part does not
apply for partitions. Also the last point does not hold for partitioned
tables because they never have NO INHERIT constraints.

We could instead perform a scan to
validate that the constraints are met. I think the way this should
work is:

1. ATTACH PARTITION works whether matching NOT NULL and CHECK
constraints are present or not.

Wouldn't this break the invariant that if the parent has a NOT NULL or a
CHECK constraint, all of its children must have it too? Remember that we
allow directly inserting/updating data into partitions, so these
constraints better be persistent.

2. If all of the constraints are present, and a validated constraint
matching the implicit partitioning constraint is also present, then
ATTACH PARTITION does not scan the table to validate constraints;
otherwise, it does.

I wonder what matching in "validated constraint matching with the implicit
partitioning constraint" means? By that do you mean an exact equal()
match, provided both have been suitably canonicalized? Or an existing
check constraint that *implies* the partitioning constraint?

3. NO VALIDATE is not an option.

I had my doubts about this option initially, but 2 above seemed hard to
do. Or perhaps we can just remove this option and we will always scan
when attaching a partition.

+      Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered, but that
+      might change in the future.

Really? Changing that sounds impractical to me.

Assuming you are questioning the "but that might change in the future"
part, I took that line from the description for INHERIT. Maybe it was
written in that section with a view that someone might implement globally
enforced variant of each of those constraints. I thought the path to that
would be slightly easier with declarative partitioning but that may be too
optimistic. Removed that part.

+      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 target table.

continues to exist as a standalone table, but no longer has any ties
to the table from which it was detached.

Done.

+      Note that if a partition being detached is itself a partitioned table,
+      it continues to exist as such.

You don't really need to say this, I think. All of the properties of
the detached table are retained, not only its partitioning status.
You wouldn't like it if I told you to document "note that if a
partition being detached is unlogged, it will still be unlogged".

I thought same as Petr who replied to this part of your email - might help
avoid confusion. But it might be redundant, so I removed that line.

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 the table as a
+   inheritance child of a parent table and attaching a table as partition to
+   the table.

To add the table as a new child of a parent table, or as a new
partition of a partitioned table, you must own the parent table as
well.

Done.

+ The name of the table to attach as a new partition to or
detach from this table.

s/to or/or to/

Oops, fixed.

+ <literal>NO VALIDATE</> option is spcified.

Typo, but see comments above about nuking this altogether.

Fixed the typo for now. As I said above it's not clear to me how the
alternative method of skipping the scan is supposed to work.

A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
-    definition of the column.  A nonrecursive <literal>DROP
+    definition of the column (which always holds if the descendant table
+    is a partition).  A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
-    instead marks them as independently defined rather than inherited.
+    instead marks them as independently defined rather than inherited,
+    unless the descendant table is a partition.

This is a hairy explanation. I suggest that the documentation say
this (and that the code implement it): A nonrecursive DROP TABLE
command will fail for a partitioned table, because all partitions of a
table must have the same columns as the partitioning root.

Agreed, done (including the code).

-    that are not marked <literal>NO INHERIT</>.
+    that are not marked <literal>NO INHERIT</> which are unsupported if
+    the table is a partitioned table.

I think you can omit this hunk.

Done.

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

I'd delete the sentence beginning with "However".

Done.

+   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</>:

s/, that is/ as a/

Fixed.

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

I think you can inline the definitions of list_spec and range_spec
here instead of making them separate productions, and I think that
would be preferable.

FOR VALUES { IN ( <replaceable
class="PARAMETER">expression</replaceable> [, ...] ) |
START <replaceable class="PARAMETER">lower-bound</replaceable> [
INCLUSIVE | EXCLUSIVE ] END <replaceable
class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
}

Ah, done that way.

+ parent table (name optionally schema-qualified).

Parenthetical phrase isn't needed.

Removed.

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

The partition bound specification must correspond to the partitioning
method and partitioning key of the parent table, and must not overlap
with any existing partition of that parent.

Done.

+      clause, if any.  Defaults and constraints can optionally be specified
+      for each of the inherited columns, which override those in the parent.

Surely not. You can't "override" an inherited constraint or an
inherited default. The child may have extra constraints not present
in the parent, and may have different defaults when it is directly
targeted by an insertion, but it can't possibly override the parent
defaults.

Removed ", 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.

The first part of this seems right, but then what's going on with the
reference to constraints in the previous sentence? (See previous
review comment.) The second sentence I would delete (but see below).

In the previous sentence, I meant the constraints that one can specify
using WITH OPTIONS [ column_constraint [...] ], but I removed
the "overrides those in the parent" part as mentioned.

Removed the second sentence and instead adopted your text below.

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

How about: Rows inserted into a partitioned table will be
automatically routed to the correct partition. If no suitable
partition exists, an error will occur.

Much better, done.

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

How about: A partition must have the same column names and types as
the table of which it is a partition. Therefore, modifications the
column names or types of the partitioned table will automatically
propagate to all children, as will operations such as TRUNCATE which
normally affect a table and all of its inheritance children. It is
also possible to TRUNCATE a partition individually, just as for an
inheritance child.

Done.

Insisting that you can't drop a child without detaching it first seems
wrong to me. If I already made this comment and you responded to it,
please point me back to whatever you said. However, my feeling is
this is flat wrong and absolutely must be changed.

I said the following [1]/messages/by-id/169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp:

| Hmm, I don't think I like this. Why should it be necessary to detach
| a partition before dropping it? That seems like an unnecessary step.

I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.

-            if (is_no_inherit)
+
+            /* Discard the NO INHERIT flag if the relation is a partition */
+            if (is_no_inherit && !rel->rd_rel->relispartition)

Something about this seems fishy. In StoreRelCheck(), you disallow
the addition of a NO INHERIT constraint on a partition, but here you
seem happy to accept it and ignore the NO INHERIT property. That
doesn't seem consistent. We should adopt a consistent policy about
what to do about such constraints, and I submit that throwing an error
is better than silently changing things, unless you have some reason
for doing that which I'm not seeing. Anyway, we should have the same
behavior in both cases.

Allowing to add NO INHERIT constraints to (leaf) partitions does not seem
like it will cause any problems not that the flag will have any meaning.
It will come into play only in one case - in the form of causing an error
if a constraint with the same name is added to the parent.

With that in mind, I removed the check in StoreRelCheck() that you mention
here and also removed the above hunk to which your comment was addressed.

We should also standardize on what value of conislocal and coninhcount
children end up with; presumably the latter should be 1, but I'm not
sure if the former should be true or false. In either case, anything
that can vary between children probably needs to be dumped, so let's
enforce that it doesn't so we don't have to dump it. I'm not sure
whether the code here achieves those objectives, though, and note the
comment in the function header about making sure the logic here
matches MergeConstraintsIntoExisting.

To summarize:

If a table is partition, it doesn't have any local attributes. Also, it
doesn't have any check constraint that is both local *and* inherited.
Only the non-inherited constraints are ever local.

So, all parent attributes will be present present in all the partitions at
any given time with attislocal = false and attinhcount = 1. Likewise for
inherited constraints with conislocal = false and coninhcount = 1.

With above rules in place, pg_dump no longer dumps attributes or inherited
constraints in CREATE TABLE commands of individual partitions.

I think the overriding principle here should be: If you attach a table
as a partition, it must not be part of a standard inheritance
hierarchy, and it must not be a partition of any other table. It can,
however, be partitioned itself. If you later detach a partition, it
ends up as a standalone table with a copy of each constraint it had as
a partition - probably including the implicit partition constraint.
The DBA can drop those constraints if they're not wanted.

Check. About implicit constraints, see below.

I wonder if it's really a good idea for the partition constraints to
be implicit; what is the benefit of leaving those uncatalogued?

I did start out that way - ie, catalogued implicit constraints, but later
thought it might not be good to end up with multiple copies of essentially
the same information. With cataloguing them will come dependencies and
all places that know about pg_constraint.

In the long term, I think we're only going to need them because we want to
enforce them when directly inserting data into partitions.

So, detaching a partition does not emit a check constraint matching the
implicit partition constraint.

+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+    struct ListInfo       *listinfo;
+    struct RangeInfo   *rangeinfo;
+} BoundCollectionData;

This seems like an odd design. First, when you have a pointer to
either of two things, the normal tool for that in C would be a union,
not a struct. Second, in PostgreSQL we typically handle that by making
both of the things nodes and then you can use IsA() or switch on
nodeTag() to figure out what you've got. Third, the only place this
is used at least in 0003 is as part of PartitionDescData, which only
has 3 members, so if you were going to do it with 2 members, you could
just include these two members directly. Considering all of the
foregoing, I'd suggest making this a union and including partstrategy
in PartitionDescData.

I think that the names ListInfo and RangeInfo are far too generic for
something that's specific to partitioning.

How about this:

/*
* Collection of bounds of a partitioned relation (either physical or
* logical)
*/
typedef struct BoundCollectionData
{
char strategy; /* list or range bounds? */

union
{
struct PartitionListInfo lists;
struct PartitionRangeInfo ranges;
} bounds;
} BoundCollectionData;

I added the strategy field here instead of PartitionDescData, because this
structure is supposed to represent even the transient partitioned
relations. If the relation is a physical relation we can determine
strategy from PartitionKey.

I renamed ListInfo/RangeInfo to PartitionListInfo/PartitionRangeInfo.

+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+    struct PartitionRange    **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+    struct PartitionRangeBound    *lower;
+    struct PartitionRangeBound    *upper;
+} PartitionRange;

This representation doesn't seem optimal, because in most cases the
lower bound of one partition will be the upper bound of the next. I
suggest that you flatten this representation into a single list of
relevant bounds, each flagged as to whether it is exclusive and
whether it is finite; and a list with one more element of bounds. For
example, suppose the partition bounds are [10, 20), [20, 30), (30,
40), and [50, 60). You first construct a list of all of the distinct
bounds, flipping inclusive/exclusive for the lefthand bound in each
case. So you get:

10 EXCLUSIVE
20 EXCLUSIVE
30 EXCLUSIVE
30 INCLUSIVE
40 EXCLUSIVE
50 EXCLUSIVE
60 EXCLUSIVE

When ordering items for this list, if the same item appears twice, the
EXCLUSIVE copy always appears before INCLUSIVE. When comparing
against an EXCLUSIVE item, we move to the first half of the array if
we are searching for a value strictly less than that value; when
comparing against an INCLUSIVE item, we move to the first half of the
array if we are searching for a value less than or equal to that
value.

This is a list of seven items, so a binary search will return a
position between 0 (less than all items) and 7 (greater than all
items). So we need a list of 8 partition mappings, which in this case
will look like this: -1, 0, 1, -1, 2, -1, 3, -1.

In this particular example, there are only two adjacent partitions, so
we end up with 7 bounds with this representation vs. 8 with yours, but
in general I think the gains will be bigger. If you've got 1000
partitions and they're all adjacent, which is likely, you store 1000
bounds instead of 2000 bounds by doing it this way.

Thanks for the idea. I have implemented the same.

+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.

Why? How about "Because this function assumes that the relation whose
OID is passed as an argument will have precisely one parent, it should
only been called when it is known that the relation is a partition."

Rewrote the note.

+    /*
+     * Translate vars in the generated expression to have correct attnos.
+     * Note that the vars in my_qual bear attnos dictated by key which carries
+     * physical attnos of the parent.  We must allow for a case where physical
+     * attnos of a partition can be different from the parent.
+     */
+    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_qual = (List *) translate_var_attno((Node *) my_qual,
+                                                       attno,
+                                                       new_attno);

It can't really be safe to do this one attribute number at a time, or
even if by some good fortune it can't be broken, it at least it seems
extremely fragile. Suppose that you translate 0 -> 3 and then 3 -> 5;
now the result is garbage. It's not very efficient to do this one
attno at a time, either.

You are quite right. I changed this to us map_variable_attnos().

When doing that, I noticed that the above function does not map whole-row
references but instead just returns whether one was encountered, so that
the caller can output an error that expressions containing whole-row vars
are not allowed in the corresponding context (examples of such callers
include transformTableLikeClause()). In context of this function, there
is no way we could output such error.

Thinking more about that, even if we did map whole-row vars in check
expressions, constraint exclusion code would not be able to handle them,
because it does not work with anything but Const node as predicate's or
query clause's right-operand.

I went ahead and prohibited whole-row references from being used in the
partition key expressions in the first place (ie, in patch 0001).

+    if (classform->relispartition)
+        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.")));
+

RangeVarCallbackForDropRelation should do only minimal sanity checks;
defer this until after we have a relation lock.

Moved this check to RemoveRelations(), wherein after
RangeVarGetRelidExtended() returns, the relation is guaranteed to be locked.

I didn't get all the way through this patch, so this is a pretty
incomplete review, but it's late here and I'm out of steam for
tonight. Some general comments:

1. I think that this patch seems to introduce an awful lot of new
structures with confusingly similar names and purposes:
PartitionBoundList, PartitionBoundRange, ListInfo, RangeInfo,
PartitionRange, PartitionList, ListValue, RangePartition. You've got
4 different structures here that all have "Partition" and "Range" in
the name someplace, including both PartitionRange and RangePartition.
Maybe there's no way around that kind of duplication; after all there
are quite a few moving parts here. But it seems like it would be good
to try to simplify it.

OK, I got rid of most of those structs and now there are only the
following: PartitionListInfo, PartitionRangeInfo, PartitionRangeBound and
PartitionListValue.

2. I'm also a bit concerned about the fairly large amount of
apparently-boilerplate code in partition.c, all having to do with how
we create all of these data structures and translate between different
forms of them. I haven't understood that stuff thoroughly enough to
have a specific idea about how we might be able to get rid of any of
it, and maybe there's no way. But that too seems like a topic for
futher investigation. One idea is that maybe some of these things
should be nodes that piggyback on the existing infrastructure in
src/backend/nodes instead of inventing a new way to do something
similar.

I thought about the idea of using the src/backend/nodes infrastructure for
partitioning data structures. However, none of those structures are part
of any existing Node nor do I imagine they will be in future, so making
them a Node seems unnecessary. But then there is a subset of NodeTags
called "TAGS FOR RANDOM OTHER STUFF" which are Node objects that not part
of parse/plan/execute node tree structures such as T_TriggerData,
T_ForeignKeyCacheInfo. I wonder if you meant to make partitioning
structures nodes of that category.

That said, I have tried in the current patch to reduce the amount of
unnecessary boilerplate code.

3. There are a lot of places where you have separate code for the
range and list partitioning cases, and I'm suspicious that there are
ways that code could be unified. For example, with list partitioning,
you have a bunch of Datums which represent the specific values that
can appear in the various partitions, and with range partitioning, you
have a bunch of Datums that represent the edges of partitions. Well,
if you used the same array for both purposes, you could use the same
code to copy it. That would involve flattening away a lot of the
subsidiary structure under PartitionDescData and pulling up stuff that
is buried lower down into the main structure, but I think that's
likely a good idea anyway - see also point #1.

Hmm, I think it might be somewhat clearer to keep them separate in the
form of PartitionListInfo and PartitionRangeInfo structs. In case of
range partitioning, each individual range bound is actually a
PartitionRangeBound instance, not just a Datum. That's because we have to
store for each range bound the following information: an array of Datums
to represent the composite partition key, is-finite flags for each
partitioning column, a flag representing whether it is a inclusive bound,
and a flag representing whether it is lower or upper bound.

4. I'm somewhat wondering if we ought to just legislate that the lower
bound is always inclusive and the upper bound is always exclusive.
The decision to support both inclusive and exclusive partition bounds
is responsible for an enormous truckload of complexity in this patch,
and I have a feeling it is also going to be a not-infrequent cause of
user error.

I thought we decided at some point to go with range type like notation to
specify range partition bound because of its flexibility. I agree though
that with that flexibility, there will more input combinations that will
cause error. As for the internal complexity, it's not clear to me whether
it will be reduced by always-inclusive lower and always-exclusive upper
bounds. We would still need to store the inclusive flag with individual
PartitionRangeBound and consider it when comparing them with each other
and with partition key of tuples.

5. I wonder how well this handles multi-column partition keys. You've
just got one Datum flag and one is-finite flag per partition, but I
wonder if you don't need to track is-finite on a per-column basis, so
that you could partition on (a, b) and have the first partition go up
to (10, 10), the second to (10, infinity), the third to (20, 10), the
fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
Oracle supports this sort of thing, so perhaps we should, too. On a

I have implemented the feature that allows specifying UNBOUNDED per column.

related note, I'm not sure it's going to work to treat a composite
partition key as a record type. The user may want to specify a
separate opfamily and collation for each column, not just inherit
whatever the record behavior is. I'm not sure if that's what you are
doing, but the relcache structures don't seem adapted to storing one
Datum per partitioning column per partition, but rather just one Datum
per partition, and I'm not sure that's going to work very well.

Actually, there *is* one Datum per partitioning column. That is,
composite partition key is not treated as a record as it may have seemed.

Please find attached the latest version of the patches taking care of the
above review comments and some other issues I found. As Amit Kapila
requested [2]/messages/by-id/CAA4eK1LqTqZkPSoonF5_cOz94OUZG9j0PNfLdhi_nPtW82fFVA@mail.gmail.com, here is a list of restrictions on partitioned tables, of
which some we might be able to overcome in future:

- cannot create indexes
- cannot create row triggers
- cannot specify UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints
- cannot become inheritance parent or child
- cannot use in COPY TO command

Thanks,
Amit

[1]: /messages/by-id/169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp
/messages/by-id/169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp

[2]: /messages/by-id/CAA4eK1LqTqZkPSoonF5_cOz94OUZG9j0PNfLdhi_nPtW82fFVA@mail.gmail.com
/messages/by-id/CAA4eK1LqTqZkPSoonF5_cOz94OUZG9j0PNfLdhi_nPtW82fFVA@mail.gmail.com

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-10.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-10.patchDownload
>From a9e30554e62a978db973364ef8e515bbc1578b30 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  160 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |    9 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  446 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 +++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   11 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  257 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   36 +++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   66 ++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  163 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   34 +++
 src/test/regress/sql/create_table.sql      |  142 +++++++++
 51 files changed, 1936 insertions(+), 48 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are used as partition key.  For example,
+       a value of <literal>1 3</literal> would mean that the first and the
+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..893c899 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partitioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions), which
+      are created using separate <literal>CREATE TABLE</> commands.
+      The table itself is empty.  A data row inserted into the table is routed
+      to a partition based on the value of columns or expressions in the
+      partition key.  If no existing partition matches the values in the new
+      row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..31a7bb4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition key, opclass info into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the partition key expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references which will count as self-dependency items; in this case,
+	 * the depender is the table itself (there is no such thing as partition
+	 * key object).
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 /*
  * Resolve possibly-defaulted operator class specification
  */
-static Oid
+Oid
 GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId)
 {
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2137372..c2ae3f8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
 								  true, true, false);
 
+	/* Process and store partition key information, if any */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier. 
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
 	ObjectAddressSet(address, RelationRelationId, relationId);
 
 	/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from table \"%s\"", parent->relname),
+					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (is_expr)
+				*is_expr = false;
+			if (attnum == partattno)
+				return true;
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			if (is_expr)
+				*is_expr = true;
+
+			/* Find all attributes referenced */
+			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;
+
+				if (attno == attnum)
+					return true;
+			}
+			partexprs_item = lnext(partexprs_item);
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5840,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5885,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7883,6 +8038,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7913,6 +8069,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7928,7 +8097,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8955,6 +9125,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9417,6 +9588,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9839,7 +10011,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10038,6 +10211,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10089,6 +10267,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from \"%s\"", parent->relname),
+				 errdetail("Table \"%s\" is partitioned.", 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.
@@ -11478,6 +11663,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11927,7 +12113,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12081,6 +12267,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12092,3 +12279,248 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partition strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears twice in partition key", pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Partition key expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = GetIndexOpClass(pelem->opclass,
+										 atttype,
+										 "btree",
+										 BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
 		case T_RoleSpec:
 			retval = _copyRoleSpec(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
 		case T_RoleSpec:
 			retval = _equalRoleSpec(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
 			case T_ForeignKeyCacheInfo:
 				_outForeignKeyCacheInfo(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -541,6 +543,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
+
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e46f879 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9f9ee5e
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partition key strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of partition key
+									 * columns */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* expression trees for partition key members
+									 * that are not simple column references; one
+									 * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	GetIndexOpClass(List *opclass, Oid attrType,
+			char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
 	T_OnConflictClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..8cc41cf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ *	'name'		Name of the table column included in the key
+ *	'expr'		Expression node tree of expressional key column
+ *	'opclass'	Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ *	'strategy'		partition strategy name (ie, 'list' and 'range')
+ *	'partParams'	List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;
+	List	   *partParams;
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partition strategy */
+	int16		partnatts;		/* number of partition attributes */
+	AttrNumber *partattrs;		/* partition attnums */
+	List	   *partexprs;		/* partition key expressions, if any */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation */
+	Oid		   *partcollation;
+
+	/* Type information of partition attributes */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a1ab823..2c913e5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,49 @@ Table "public.test_add_column"
  c4     | integer | 
 
 DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR:  relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR:  relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR:  cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..c6ed153 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,166 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+ERROR:  syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+          ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE fail_wholerow_ref (
+	a	int
+) PARTITION BY RANGE ((fail_wholerow_ref));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind 
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table dependency_matters depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR:  cannot inherit from table "no_inh_parted"
+DETAIL:  Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..3dbeb48 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,37 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+	a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6a2f8f6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,145 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+	a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_subquery_in_key (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_col (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE fail_wholerow_ref (
+	a	int
+) PARTITION BY RANGE ((fail_wholerow_ref));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+	a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+	a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
-- 
1.7.1


0002-psql-and-pg_dump-support-for-partitioned-tables-10.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-10.patchDownload
>From 3852d78da0f278d25a2b7481b1219ce69d9fc37a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  146 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  134 ++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/bin/psql/describe.c                    |   61 +++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 366 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..7279151 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,150 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	/*
+	 * Get the partition key expressions, if any.  (NOTE: we do not use the
+	 * relcache versions of the expressions, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	/*
+	 * Start the partition key definition.
+	 */
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	/*
+	 * Report the partition key columns
+	 */
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple partition key column */
+			char	   *attname;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			keycoltype = get_atttype(relid, attnum);
+		}
+		else
+		{
+			/* partition key expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+		}
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..067353d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4849,6 +4853,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -4873,7 +4878,108 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90600)
+	if (fout->remoteVersion >= 100000)
+	{
+		PQExpBuffer acl_subquery = createPQExpBuffer();
+		PQExpBuffer racl_subquery = createPQExpBuffer();
+		PQExpBuffer initacl_subquery = createPQExpBuffer();
+		PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+		PQExpBuffer attacl_subquery = createPQExpBuffer();
+		PQExpBuffer attracl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+		PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 *
+		 * Left join to detect if any privileges are still as-set-at-init, in
+		 * which case we won't dump out ACL commands for those.
+		 */
+
+		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+						initracl_subquery, "c.relacl", "c.relowner",
+				 "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+						dopt->binary_upgrade);
+
+		buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+					  attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+						dopt->binary_upgrade);
+
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "%s AS relacl, %s as rrelacl, "
+						  "%s AS initrelacl, %s as initrrelacl, "
+						  "c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relrowsecurity, c.relforcerowsecurity, "
+						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "tc.relminmxid AS tminmxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "c.relreplident, c.relpages, "
+						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+						  "d.refobjid AS owning_tab, "
+						  "d.refobjsubid AS owning_col, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						  "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+						  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+						  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+						  "tc.reloptions AS toast_reloptions, "
+						  "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = at.attnum)"
+						  "WHERE at.attrelid = c.oid AND ("
+						  "%s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL "
+						  "OR %s IS NOT NULL"
+						  "))"
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+						  "LEFT JOIN pg_init_privs pip ON "
+						  "(c.oid = pip.objoid "
+						  "AND pip.classoid = 'pg_class'::regclass "
+						  "AND pip.objsubid = 0) "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  acl_subquery->data,
+						  racl_subquery->data,
+						  initacl_subquery->data,
+						  initracl_subquery->data,
+						  username_subquery,
+						  attacl_subquery->data,
+						  attracl_subquery->data,
+						  attinitacl_subquery->data,
+						  attinitracl_subquery->data,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
+
+		destroyPQExpBuffer(acl_subquery);
+		destroyPQExpBuffer(racl_subquery);
+		destroyPQExpBuffer(initacl_subquery);
+		destroyPQExpBuffer(initracl_subquery);
+
+		destroyPQExpBuffer(attacl_subquery);
+		destroyPQExpBuffer(attracl_subquery);
+		destroyPQExpBuffer(attinitacl_subquery);
+		destroyPQExpBuffer(attinitracl_subquery);
+	}
+	else if (fout->remoteVersion >= 90600)
 	{
 		PQExpBuffer acl_subquery = createPQExpBuffer();
 		PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5375,6 +5481,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -5444,6 +5551,10 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+			tblinfo[i].partkeydef = NULL;
+		else
+			tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5488,7 +5599,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -5590,7 +5703,10 @@ getInherits(Archive *fout, int *numInherits)
 
 	/* find all the inheritance information */
 
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -14249,6 +14365,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14309,6 +14428,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14447,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14544,6 +14665,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a60cf95..e4db8db 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1567,8 +1569,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		show_modifiers = true;
 		headers[cols++] = gettext_noop("Modifiers");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1772,7 +1782,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1780,14 +1790,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1926,7 +1955,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2485,7 +2514,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2696,7 +2725,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 50a45eb..8284a9c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index c6ed153..dbc05b9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -416,3 +416,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6a2f8f6..56aefb4 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -411,3 +411,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


0003-Catalog-and-DDL-for-partitions-10.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-10.patchDownload
>From 49a44b23fcddca5884db04bcdc24844415cc478f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.

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

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  101 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   26 +
 doc/src/sgml/ref/create_table.sgml         |   92 ++-
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |   72 ++-
 src/backend/catalog/partition.c            | 1702 ++++++++++++++++++++++++++++
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  751 +++++++++++--
 src/backend/nodes/copyfuncs.c              |   51 +
 src/backend/nodes/equalfuncs.c             |   45 +
 src/backend/nodes/nodeFuncs.c              |    6 +
 src/backend/nodes/outfuncs.c               |   30 +
 src/backend/nodes/readfuncs.c              |   36 +
 src/backend/parser/gram.y                  |  238 ++++-
 src/backend/parser/parse_agg.c             |    1 -
 src/backend/parser/parse_utilcmd.c         |  358 ++++++-
 src/backend/utils/cache/relcache.c         |  105 ++-
 src/include/catalog/heap.h                 |    1 +
 src/include/catalog/partition.h            |   58 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   53 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  228 ++++
 src/test/regress/expected/create_table.out |  200 ++++
 src/test/regress/sql/alter_table.sql       |  193 ++++
 src/test/regress/sql/create_table.sql      |  155 +++
 29 files changed, 4429 insertions(+), 142 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..cf8452c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,48 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as partition of the target table.  The partition bound specification must
+      correspond to the partitioning method and partitioning key of the target
+      table.  The table to be attached must have all the columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the matching constraints as the target table.
+      That includes both <literal>NOT NULL</literal> and <literal>CHECK</literal>
+      constraints.  If some <literal>CHECK</literal> constraint of the table
+      being attached is marked <literal>NO INHERIT</literal>, the command will
+      fail; such constraints must either be dropped or recreated without the
+      <literal>NO INHERIT</literal> mark.  Currently <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+      constraints are not considered.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +770,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +987,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1045,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 specified.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1120,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1309,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..f05f212 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES 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 893c899..6e01dbb 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | START ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] END ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +247,50 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partitioning key of the parent table, and must not overlap
+      with any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints, in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (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 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 31a7bb4..2b63665 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -810,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +822,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +930,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -2468,8 +2475,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2514,10 +2524,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * If rel is a partition, a constraint is forced to be non-local,
+			 * after merging with the parent's constraint.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->conislocal = false;
+				if (!is_local || con->coninhcount == 0)
+					con->coninhcount++;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3170,3 +3194,43 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation		classRel;
+	Form_pg_class	classForm;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	isnull,
+			new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+	classForm = (Form_pg_class) GETSTRUCT(tuple);
+	Assert(!classForm->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(isnull);
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..3ec148b
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1702 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; has
+						 * same length as the values array above */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;			/* range bound datums */
+	bool   *infinite;		/* infinite flag for each datum */
+	bool	inclusive;		/* bound is inclusive (vs exclusive) */
+	bool	lower;			/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, of which we only store one.
+ */
+typedef struct PartitionRangeInfo
+{
+	PartitionRangeBound **bounds;
+	int		nbounds;
+	int	   *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+	char	strategy;	/* list or range bounds? */
+
+	union
+	{
+		struct PartitionListInfo	lists;
+		struct PartitionRangeInfo	ranges;
+	} bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	Datum	value;
+	int		index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums,
+					 bool inclusive, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+												  PartitionRangeBound *,
+												  void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	PartitionListValue **all_values;
+	int			all_values_count;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	PartitionRangeBound **distinct_bounds;
+	int			num_distinct_bounds;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.  XXX - perhaps it would be better if we
+		 * did not need to use the parent relation desc in that case, because
+		 * the following looks kind of ugly.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		ReleaseSysCache(tuple);
+		partoids = lappend_oid(partoids, inhrelid);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = datumCopy(val->constvalue,
+														  key->parttypbyval[0],
+														  key->parttyplen[0]);
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							found_null_partition = true;
+							null_partition_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				all_values_count = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **)
+								  palloc(all_values_count *
+										sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = datumCopy(src->value,
+													 key->parttypbyval[0],
+													 key->parttyplen[0]);
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, all_values_count,
+						  sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp,
+						  (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+					lower = make_one_range_bound(key, i,
+												 spec->lowerdatums,
+												 spec->lowerinc, true);
+					/*
+					 * In most cases, upper bound of the  i'th partition is
+					 * equal in *value* to the lower bound of the (i+1)'th
+					 * partition, execpt they have different inclusivity.
+					 * We want to keep only the former in the final list of
+					 * bounds by eliminating the latter.  To be able to equate
+					 * the two, we flip the inclusivity flag of the latter,
+					 * so that it matches the former's flag value in exactly
+					 * the cases where the elimination must occur.
+					 */
+					lower->inclusive = !lower->inclusive;
+
+					upper = make_one_range_bound(key, i,
+												 spec->upperdatums,
+												 spec->upperinc, false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				num_distinct_bounds = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.
+					 */
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						num_distinct_bounds++;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				distinct_bounds = (PartitionRangeBound **)
+											palloc0(num_distinct_bounds *
+												sizeof(PartitionRangeBound *));
+				k = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						distinct_bounds[k++] = cur;
+
+					/*
+					 * If it's a lower bound, flip the inclusive flag to its
+					 * original value.
+					 */
+					if (cur->lower)
+						cur->inclusive = !cur->inclusive;
+
+					prev = cur;
+				}
+				Assert(k == num_distinct_bounds);
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->boundinfo = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		result->boundinfo->strategy = key->strategy;
+
+		/* Allocate and initialize with invalid mapping values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionListInfo	listinfo;
+
+				listinfo.nvalues = all_values_count;
+				listinfo.has_null = found_null_partition;
+				listinfo.values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo.indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they match for any two list partitioned tables
+				 * with same number of partitions and same lists per partition.
+				 * One way to achieve this is to assign indexes in the same
+				 * order as the least values of the individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo.values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo.indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo.null_index = mapping[null_partition_index];
+				else
+					listinfo.null_index = -1;
+
+				result->boundinfo->bounds.lists = listinfo;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeInfo	rangeinfo;
+
+				rangeinfo.bounds = (PartitionRangeBound **)
+										palloc(num_distinct_bounds *
+											sizeof(PartitionRangeBound *));
+				rangeinfo.indexes = (int *)
+										palloc((num_distinct_bounds + 1) *
+													sizeof(int));
+
+				for (i = 0; i < num_distinct_bounds; i++)
+				{
+					rangeinfo.bounds[i] =
+								copy_range_bound(key, distinct_bounds[i]);
+
+					/* There is no mapping for invalid indexes (ie, -1) */
+					if (rangeinfo.bounds[i]->lower)
+						rangeinfo.indexes[i] = -1;
+					else
+					{
+						int		orig_index = rangeinfo.bounds[i]->index;
+
+						/* If this index has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						rangeinfo.indexes[i] = mapping[orig_index];
+					}
+				}
+				rangeinfo.indexes[i] = -1;
+				rangeinfo.nbounds = num_distinct_bounds;
+
+				result->boundinfo->bounds.ranges = rangeinfo;
+				break;
+			}
+		}
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds (ie, sorted).
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2)
+{
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key,
+								 &b1->bounds.lists, &b2->bounds.lists))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key,
+								  &b1->bounds.ranges, &b2->bounds.ranges))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	Relation		parent;
+	PartitionKey	key;
+	PartitionDesc	pdesc;
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	parent = heap_open(parentId, NoLock); /* already locked */
+	key = RelationGetPartitionKey(parent);
+	pdesc = RelationGetPartitionDesc(parent);
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (pdesc->nparts > 0)
+			{
+				ListCell   *cell;
+				bool		new_has_null = false;
+				PartitionListInfo	listinfo;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+				listinfo = pdesc->boundinfo->bounds.lists;
+				Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+				/* If the new partition's list contains null, set a flag. */
+				foreach (cell, spec->listdatums)
+				{
+					Const *val = lfirst(cell);
+
+					if (val->constisnull)
+					{
+						new_has_null = true;
+						break;
+					}
+				}
+
+				/*
+				 * Now check against listinfo whether the new partition's
+				 * values are not already taken by existing partitions.
+				 */
+				if (new_has_null && listinfo.has_null)
+				{
+					with = listinfo.null_index;
+					overlap = true;
+				}
+				else
+				{
+					foreach (cell, spec->listdatums)
+					{
+						Const  *val = lfirst(cell);
+						int		found = -1;
+
+						/* bsearch a new list's value in listinfo->values */
+						if (!val->constisnull)
+							found = partition_list_values_bsearch(key,
+														listinfo.values,
+														listinfo.nvalues,
+														val->constvalue);
+						if (found >= 0)
+						{
+							overlap = true;
+							with = listinfo.indexes[found];
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1,
+										 spec->lowerdatums, spec->lowerinc,
+										 true);
+			upper = make_one_range_bound(key, -1,
+										 spec->upperdatums, spec->upperinc,
+										 false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified bounds
+			 */
+			if (partition_rbound_cmp(key, lower, upper) > 0 ||
+				(partition_rbound_cmp(key, lower, upper) == 0
+					&& (!spec->lowerinc || !spec->upperinc)))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				PartitionRangeInfo rangeinfo;
+				int		  offset1, offset2;
+				bool	  equal = false;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+				rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than the new lower bound.
+				 */
+				offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+											  rangeinfo.nbounds, lower,
+											  partition_rbound_cmp, false, &equal);
+
+				/*
+				 * If equal has been set to true, that means the new bound is
+				 * found to be equal to some existing bound.  offset1 == -1
+				 * means that all existing range bounds are greater than the
+				 * new bound.  If offset >= 0, and there exists a gap between
+				 * the corresponding bound and the next, new partition can
+				 * still manage to fit within the same. To confirm whether it
+				 * is so, check the new upper bound.
+				 */
+				if (!equal &&
+					(offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+				{
+					offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+												  rangeinfo.nbounds, upper,
+												  partition_rbound_cmp, false,
+												  &equal);
+
+					if (equal || offset1 != offset2)
+					{
+						overlap = true;
+						with = rangeinfo.indexes[offset2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					if (offset1 == -1)
+					{
+						Assert(equal);
+						offset1 = 0;
+					}
+					with = rangeinfo.indexes[offset1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(pdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ *		Returns a list of all leaf partitions of relation with OID 'relid'
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+	List	   *partitions,
+			   *result = NIL;
+	ListCell   *lc;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	partitions = find_inheritance_children(relid, lockmode);
+
+	foreach(lc, partitions)
+	{
+		Oid		myoid = lfirst_oid(lc);
+		Relation partRel;
+
+		partRel = heap_open(myoid, NoLock);
+
+		if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			result = list_concat(result,
+								 get_leaf_partition_oids(myoid, lockmode));
+		else
+			result = lappend_oid(result, myoid);
+
+		heap_close(partRel, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference in there*/
+	Assert(!found_whole_row);
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If the list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, remove the null Const from the list and create a IS NULL
+	 * test and create an OR expression along with ScalarArrayOpExpr. We do
+	 * it this way because the null-valued expression does not have the
+	 * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		BoolExpr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key emitting an OpExpr for each, based
+	 * on the corresponding elements of the lower and upper datums.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *key_col;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		NullTest	   *nulltest;
+		Oid			operoid;
+		uint16		strategy;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * If either of lower or upper datum is infinite, stop at this column,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * Is lower_val = upper_val?
+		 */
+		if (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->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* Yes, build leftop eq lower_val */
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					key_col = (Node *) makeRelabelType((Expr *) key_col,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) key_col,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		if (lower_val)
+		{
+			/* Build leftop ge/gt lower_val */
+			strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+										: BTGreaterStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			/* Build leftop le/lt upper_val */
+			strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+											: BTLessStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column for we cannot say anything about the
+		 * further columns, because they were not checked during tuple-routing
+		 * for any row that made into this partition. In other words,
+		 * comparing corresponding columns of an input row with this pair of
+		 * lower and upper datums would have conclusively determined whether
+		 * the row maps to this partition or not.
+		 */
+		break;
+	}
+
+	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->parttypid[col],
+								  key->parttypid[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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, AccessShareLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			value1 = (*(const PartitionListValue **) a)->value,
+					value2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = partition_list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums,
+					 bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+	bound->inclusive = inclusive;
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		bound->infinite[i] = datum->infinite;
+
+		if (!bound->infinite[i])
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot specify NULL in range bound")));
+			else
+				bound->datums[i] = datumCopy(val->constvalue,
+											 key->parttypbyval[i],
+											 key->parttyplen[i]);
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	result->index = src->index;
+	result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+	result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+	result->inclusive = src->inclusive;
+	result->lower = src->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		result->infinite[i] = src->infinite[i];
+		if (!result->infinite[i])
+			result->datums[i] = datumCopy(src->datums[i],
+										  key->parttypbyval[i],
+										  key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	int		i;
+
+	if (r1->nbounds != r2->nbounds)
+		return false;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < r1->nbounds; i++)
+	{
+		if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+			return false;
+
+		if (r1->indexes[i] != r2->indexes[i])
+			return false;
+	}
+
+	/* There is one more index than bounds */
+	if (r1->indexes[i] != r2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+	PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int32	cmpval;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (b1->infinite[i] && b2->infinite[i])
+		{
+			/*
+			 * Both are infinity, so they are equal unless one is lower and
+			 * the other not.
+			 */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? -1 : 1;
+		}
+		else if (b1->infinite[i])
+			return b1->lower ? -1 : 1;
+		else if (b2->infinite[i])
+			return b2->lower ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.
+	 */
+	if (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		int32	cmpval = 0;
+
+		/*
+		 * If either of them has infinite element, we can't equate them.
+		 * Even if both were infinite, they'd have opposite signs (it always
+		 * holds that b1 and b2 are lower and upper bound or vice versa).
+		 */
+		if (b1->infinite[i] || b2->infinite[i])
+			return false;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			return false;
+	}
+
+	/*
+	 * All datums are found to be equal, now only if both are inclusive
+	 * or exclusive then they are considered equal.
+	 */
+	if (b1->inclusive == b2->inclusive)
+		return true;
+
+	return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+    PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+    PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple.  The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal)
+{
+	int		lo, hi, mid;
+
+	lo = -1;
+	hi = n - 1;
+	while (lo < hi)
+	{
+		PartitionRangeBound *cur;
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cur = bounds[mid];
+
+		cmpval = cmp(key, cur, probe);
+		if (is_equal)
+			*is_equal = (cmpval == 0);
+
+		if (cmpval < 0 || (equal_allowed && cmpval == 0))
+			lo = mid;
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 c2ae3f8..dc28393 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -163,6 +164,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 */
+	List	   *partition_quals; /* partition check quals 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 */
@@ -267,6 +270,7 @@ struct DropRelationCallbackState
 	char		relkind;
 	Oid			heapOid;
 	bool		concurrent;
+	bool		relispartition;
 };
 
 /* Alter table target-type flags for ATSimplePermissions */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -594,10 +606,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
@@ -717,6 +735,29 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	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(relname, parentId, stmt->partbound);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, stmt->partbound);
+
+		/*
+		 * The following code may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
@@ -938,6 +979,7 @@ RemoveRelations(DropStmt *drop)
 		state.relkind = relkind;
 		state.heapOid = InvalidOid;
 		state.concurrent = drop->concurrent;
+		state.relispartition = false;
 		relOid = RangeVarGetRelidExtended(rel, lockmode, true,
 										  false,
 										  RangeVarCallbackForDropRelation,
@@ -950,6 +992,13 @@ RemoveRelations(DropStmt *drop)
 			continue;
 		}
 
+		/* Cannot drop a partition directly. */
+		if (state.relispartition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot drop partition \"%s\"", rel->relname),
+					 errdetail("You must detach it from the parent first.")));
+
 		/* OK, we're ready to delete this one */
 		obj.classId = RelationRelationId;
 		obj.objectId = relOid;
@@ -1004,6 +1053,12 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
 	/*
+	 * Return whether this relation is actually a partition that must not be
+	 * dropped on its own.
+	 */
+	state->relispartition = classform->relispartition;
+
+	/*
 	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
 	 * but RemoveRelations() can only pass one relkind for a given relation.
 	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
@@ -1093,7 +1148,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1471,7 +1527,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;
@@ -1516,8 +1573,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),
@@ -1544,6 +1601,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),
@@ -1579,18 +1648,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from table \"%s\"", parent->relname),
 					 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1816,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;
@@ -1856,6 +1945,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)
 	{
@@ -1885,16 +1977,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);
@@ -1931,8 +2027,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 */
@@ -2452,7 +2549,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -3126,6 +3223,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);
@@ -3237,12 +3339,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3443,6 +3547,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);
@@ -3513,7 +3623,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, following
+		 * check is unnecessary.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3762,6 +3878,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);
@@ -3947,7 +4069,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4027,6 +4149,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4091,6 +4214,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4412,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("child table contains a row violating partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4615,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4799,6 +4938,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5245,6 +5389,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5320,6 +5477,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
@@ -5352,6 +5526,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5912,6 +6100,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7911,6 +8108,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10212,6 +10419,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10224,12 +10436,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;
 
@@ -10274,37 +10481,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from \"%s\"", parent->relname),
 				 errdetail("Table \"%s\" is partitioned.", parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10339,6 +10520,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);
 
@@ -10353,16 +10597,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;
 }
 
 /*
@@ -10413,7 +10649,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
@@ -10431,12 +10667,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10476,14 +10716,22 @@ 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 child table must be marked NOT NULL",
+								attributeName)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (is_attach_partition)
+				childatt->attislocal = false;
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10506,7 +10754,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.
@@ -10525,10 +10773,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10605,6 +10857,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of conislocal
+			 * is same in all partitions for inherited constraints.
+			 */
+			if (is_attach_partition)
+				child_con->conislocal = false;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10629,6 +10889,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10642,13 +10942,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];
@@ -10657,19 +10955,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10679,7 +10968,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);
 
@@ -10700,11 +10989,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
@@ -10713,7 +11011,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)))
@@ -10775,7 +11073,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);
 
@@ -10806,7 +11104,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)
@@ -10818,30 +11116,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;
 }
 
 /*
@@ -12524,3 +12812,242 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRel;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, 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(inheritsRel, 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 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.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+												 AccessExclusiveLock);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..90d89d2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,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);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4202,6 +4204,46 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_SCALAR_FIELD(lowerinc);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_SCALAR_FIELD(upperinc);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+	COPY_SCALAR_FIELD(skip_validate);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5122,6 +5164,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a6421d2..12f3438 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,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);
@@ -2657,6 +2659,40 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_SCALAR_FIELD(lowerinc);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_SCALAR_FIELD(upperinc);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+	COMPARE_SCALAR_FIELD(skip_validate);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3416,6 +3452,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 417e20a..9e7ae1f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -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);
@@ -3290,6 +3292,28 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_BOOL_FIELD(lowerinc);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_BOOL_FIELD(upperinc);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3880,6 +3904,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..655aa1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,38 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_BOOL_FIELD(lowerinc);
+	READ_NODE_FIELD(lowerdatums);
+	READ_BOOL_FIELD(upperinc);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2529,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..17468b2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -547,6 +548,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <boolean>		lb_inc ub_inc
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
+%type <boolean>		opt_validate_spec
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -572,7 +582,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
@@ -588,7 +598,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
@@ -602,7 +613,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
 
@@ -2374,6 +2385,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:
@@ -2469,6 +2512,88 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START '(' range_datum_list ')' lb_inc
+						 END_P '(' range_datum_list ')' ub_inc
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerinc = $7;
+					n->lowerdatums = $5;
+					n->upperinc = $12;
+					n->upperdatums = $10;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+lb_inc:
+			EXCLUSIVE		{ $$ = false;}
+			| INCLUSIVE		{ $$ = true; }
+			| /* EMPTY */	{ $$ = true; }
+		;
+
+ub_inc:
+			INCLUSIVE		{ $$ = true; }
+			| EXCLUSIVE		{ $$ = false; }
+			| /* EMPTY */	{ $$ = false; }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2886,6 +3011,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2931,6 +3094,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2953,6 +3121,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2964,6 +3143,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2973,6 +3157,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;
@@ -2994,6 +3179,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;
@@ -4551,6 +4737,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11156,6 +11384,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;
@@ -13755,6 +13984,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13801,6 +14031,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13843,6 +14074,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 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 
 			break;
 
-
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..c0931b2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +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 */
@@ -130,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);
 
 
 /*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partspec != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -249,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->partspec)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -359,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);
@@ -898,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;
@@ -1117,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;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,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;
@@ -3026,3 +3059,326 @@ 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_TABLE)
+		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 (!cxt->hasoids && parentRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+
+	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_TABLE)
+		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 specification as per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+
+				value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+				value = coerce_to_target_type(cxt->pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(cxt->pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) > partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("START has more values than the length of the partition key"),
+							parser_errposition(cxt->pstate,
+								exprLocation(list_nth(spec->lowerdatums,
+									 list_length(spec->lowerdatums) - 1)))));
+			else if (list_length(spec->lowerdatums) < partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("START has fewer values than the length of the partition key"),
+							parser_errposition(cxt->pstate,
+								exprLocation(list_nth(spec->lowerdatums,
+									 list_length(spec->lowerdatums) - 1)))));
+
+			if (list_length(spec->upperdatums) > partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("END has more values than the length of the partition key"),
+							parser_errposition(cxt->pstate,
+								exprLocation(list_nth(spec->upperdatums,
+									 list_length(spec->upperdatums) - 1)))));
+			else if (list_length(spec->upperdatums) < partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("END has fewer values than the length of the partition key"),
+							parser_errposition(cxt->pstate,
+								exprLocation(list_nth(spec->upperdatums,
+									 list_length(spec->upperdatums) - 1)))));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+								    *rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				/*if (ldatum->infinite && rdatum->infinite)
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("both START and END datum for column \"%s\" cannot be UNBOUNDED",
+								colname),
+						 parser_errposition(cxt->pstate, rdatum->location)));*/
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(cxt->pstate, &lcon->val, lcon->location);
+					value = coerce_to_target_type(cxt->pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(cxt->pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(cxt->pstate, &rcon->val, rcon->location);
+					value = coerce_to_target_type(cxt->pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(cxt->pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e46f879..d88749a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? No mind order - in the list case, it matches the order
+		 * in which partition oids are returned by a pg_inherits scan, whereas
+		 * in the range case, they are in order of ranges of individual
+		 * partitions.  XXX - is the former unsafe?
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The iteration logic is
+		 * local to partition.c.
+		 */
+		if (pdesc1->boundinfo != NULL)
+		{
+			if (pdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key,
+										pdesc1->boundinfo, pdesc2->boundinfo))
+				return false;
+		}
+		else if (pdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2609,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 */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..8df8454
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c.  Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..7d7651c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8cc41cf..62a189d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,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) */
@@ -735,6 +736,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/* Range partition lower and upper bound spec*/
+	bool		lowerinc;
+	List	   *lowerdatums;
+	bool		upperinc;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1562,7 +1608,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
@@ -1788,7 +1836,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 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/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 2c913e5..becbca4 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3020,3 +3020,231 @@ ERROR:  cannot change inheritance of partitioned table
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES START (1) ...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not already a child table
+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 that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+ERROR:  child table contains a row violating partition bound specification
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+ERROR:  child table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check that 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 that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR:  cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index dbc05b9..e391f17 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -442,3 +442,203 @@ Table "public.describe_list_key"
 Partition key: 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 literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES START (1) ...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR:  START has more values than the length of the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+                                                             ^
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR:  END has more values than the length of the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+                                                                    ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE fail_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;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES IN ('a') WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+ERROR:  cannot drop partition "part_a"
+DETAIL:  You must detach it from the parent first.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 17 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 3dbeb48..a20cac2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1909,3 +1909,196 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not already a child table
+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 that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 56aefb4..13d9ce2 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -424,3 +424,158 @@ 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 literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES IN ('a') WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1


0004-psql-and-pg_dump-support-for-partitions-10.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-10.patchDownload
>From e002ca35c5a36eb2e448f4d1484cad09a628bd95 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   86 +++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 +++++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  105 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 +++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 409 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7279151..1c30767 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,92 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " START");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						if (!spec->lowerinc)
+							appendStringInfoString(buf, " EXCLUSIVE");
+
+						appendStringInfoString(buf, " END");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						if (spec->upperinc)
+							appendStringInfoString(buf, " INCLUSIVE");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 067353d..e2726af 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5733,6 +5733,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find all the inheritance information */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14223,6 +14287,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14251,8 +14326,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14281,7 +14359,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14339,15 +14418,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14517,6 +14603,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e4db8db..4973cd1 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e391f17..23fc457 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -611,6 +611,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- Partition bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+         Table "public.parted"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  cannot drop partition "part_a"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 13d9ce2..cdfa783 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -572,6 +572,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1


0005-Refactor-optimizer-s-inheritance-set-expansion-code-10.patchtext/x-diff; name=0005-Refactor-optimizer-s-inheritance-set-expansion-code-10.patchDownload
>From 32a42cb2b6636474b0772816150bba8681c0b41b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.

Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios.  That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.

Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.

Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
 src/backend/optimizer/plan/initsplan.c |   17 ++-
 src/backend/optimizer/prep/prepunion.c |  282 +++++++++++++++++++++++---------
 src/backend/optimizer/util/plancat.c   |    9 +-
 3 files changed, 224 insertions(+), 84 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
 	{
 		RelOptInfo *brel = root->simple_rel_array[rti];
+		RangeTblEntry *rte = root->simple_rte_array[rti];
 
-		if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+		if (brel == NULL)
+			continue;
+
+		/*
+		 * If an "other rel" RTE is a "partitioned table", we must propagate
+		 * the lateral info inherited all the way from the root parent to its
+		 * children. That's because the children are not linked directly with
+		 * the root parent via AppendRelInfo's unlike in case of a regular
+		 * inheritance set (see expand_inherited_rtentry()).  Failing to
+		 * do this would result in those children not getting marked with the
+		 * appropriate lateral info.
+		 */
+		if (brel->reloptkind != RELOPT_BASEREL &&
+			rte->relkind != RELKIND_PARTITIONED_TABLE)
 			continue;
 
 		if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
 static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
 static List *adjust_inherited_tlist(List *tlist,
 					   AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							 Index rti, PlanRowMark *oldrc,
+							 LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex);
 
 
 /*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
 
 	/*
 	 * expand_inherited_rtentry may add RTEs to parse->rtable; there is no
-	 * need to scan them since they can't have inh=true.  So just scan as far
+	 * need to scan them here since they can't normally have inh=true.  If
+	 * the inheritance set represents a partitioned table, some newly added
+	 * RTEs will break the above rule if they are partitioned tables
+	 * themselves, but they are expanded recursively.  So just scan as far
 	 * as the original end of the rtable list.
 	 */
 	nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
  *		"inh" flag to prevent later code from looking for AppendRelInfos.
  *
  * Note that the original RTE is considered to represent the whole
- * inheritance set.  The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set.  If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively.  The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set.  The same applies
+ * to each individual inheritance set in the recursive expansion case.
  *
  * A childless table is never considered to be an inheritance set; therefore
  * a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	Query	   *parse = root->parse;
 	Oid			parentOID;
 	PlanRowMark *oldrc;
-	Relation	oldrelation;
 	LOCKMODE	lockmode;
-	List	   *inhOIDs;
 	List	   *appinfos;
-	ListCell   *l;
 
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	else
 		lockmode = AccessShareLock;
 
-	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+	/*
+	 * Do not flatten the inheritance hierarchy if partitioned table, unless
+	 * this is the result relation.
+	 */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+		rti != root->parse->resultRelation)
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, false);
+	else
+		appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+												 lockmode, true);
+
+	/* Add to root->append_rel_list */
+	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ *		Expand an inheritance set in either non-recursive (flatten=true) or
+ *		recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table.  This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+							  Index rti, PlanRowMark *oldrc,
+							  LOCKMODE lockmode, bool flatten)
+{
+	Oid			parentOID;
+	Relation	oldrelation;
+	List	   *inhOIDs;
+	List	   *appinfos = NIL;
+	ListCell   *l;
+	bool		has_descendents;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	parentOID = rte->relid;
 
 	/*
-	 * Check that there's at least one descendant, else treat as no-child
+	 * Get the list of inheritors.
+	 *
+	 * Also check that there's at least one descendant, else treat as no-child
 	 * case.  This could happen despite above has_subclass() check, if table
 	 * once had a child but no longer does.
 	 */
-	if (list_length(inhOIDs) < 2)
+	if (flatten)
+	{
+		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+		has_descendents = list_length(inhOIDs) >= 2;
+	}
+	else
+	{
+		inhOIDs = find_inheritance_children(parentOID, lockmode);
+		has_descendents = list_length(inhOIDs) >= 1;
+	}
+
+	if (!has_descendents)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
 
 	/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	 */
 	oldrelation = heap_open(parentOID, NoLock);
 
+	/*
+	 * Process parent relation in its role as inheritance set member; remember
+	 * that parent table OID is not in inhOIDs if we did not flatten the
+	 * inheritance tree.
+	 */
+	if (!flatten)
+		appinfos = list_make1(process_one_child_table(root, rte, rti,
+													  oldrelation, oldrelation,
+													  oldrc, false,
+													  NULL, NULL));
+
 	/* Scan the inheritance set and expand it */
-	appinfos = NIL;
 	foreach(l, inhOIDs)
 	{
 		Oid			childOID = lfirst_oid(l);
 		Relation	newrelation;
 		RangeTblEntry *childrte;
 		Index		childRTindex;
-		AppendRelInfo *appinfo;
 
 		/* Open rel if needed; we already have required locks */
 		if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		}
 
 		/*
-		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID
-		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
-		 * since all required permissions checks are done on the original RTE.
-		 */
-		childrte = copyObject(rte);
-		childrte->relid = childOID;
-		childrte->relkind = newrelation->rd_rel->relkind;
-		childrte->inh = false;
-		childrte->requiredPerms = 0;
-		parse->rtable = lappend(parse->rtable, childrte);
-		childRTindex = list_length(parse->rtable);
-
-		/*
-		 * Build an AppendRelInfo for this parent and child.
-		 */
-		appinfo = makeNode(AppendRelInfo);
-		appinfo->parent_relid = rti;
-		appinfo->child_relid = childRTindex;
-		appinfo->parent_reltype = oldrelation->rd_rel->reltype;
-		appinfo->child_reltype = newrelation->rd_rel->reltype;
-		make_inh_translation_list(oldrelation, newrelation, childRTindex,
-								  &appinfo->translated_vars);
-		appinfo->parent_reloid = parentOID;
-		appinfos = lappend(appinfos, appinfo);
-
-		/*
-		 * Translate the column permissions bitmaps to the child's attnums (we
-		 * have to build the translated_vars list before we can do this). But
-		 * if this is the parent table, leave copyObject's result alone.
+		 * process_one_child_table() performs the following actions for the
+		 * child table:
 		 *
-		 * Note: we need to do this even though the executor won't run any
-		 * permissions checks on the child RTE.  The insertedCols/updatedCols
-		 * bitmaps may be examined for trigger-firing purposes.
-		 */
-		if (childOID != parentOID)
-		{
-			childrte->selectedCols = translate_col_privs(rte->selectedCols,
-												   appinfo->translated_vars);
-			childrte->insertedCols = translate_col_privs(rte->insertedCols,
-												   appinfo->translated_vars);
-			childrte->updatedCols = translate_col_privs(rte->updatedCols,
-												   appinfo->translated_vars);
-		}
-
-		/*
-		 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+		 * 1. add a new RTE to the query rtable,
+		 * 2. builds a PlanRowMark and adds to the root->rowMarks list
+		 * 3. builds and returns AppendRelInfo for parent-child pair
 		 */
-		if (oldrc)
+		appinfos = lappend(appinfos,
+						   process_one_child_table(root, rte, rti,
+												   oldrelation, newrelation,
+												   oldrc, false,
+												   &childrte, &childRTindex));
+
+		/* Recurse if we did not flatten the inheritance tree */
+		if (!flatten && has_subclass(childOID))
 		{
-			PlanRowMark *newrc = makeNode(PlanRowMark);
-
-			newrc->rti = childRTindex;
-			newrc->prti = rti;
-			newrc->rowmarkId = oldrc->rowmarkId;
-			/* Reselect rowmark type, because relkind might not match parent */
-			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
-			newrc->allMarkTypes = (1 << newrc->markType);
-			newrc->strength = oldrc->strength;
-			newrc->waitPolicy = oldrc->waitPolicy;
-			newrc->isParent = false;
-
-			/* Include child's rowmark type in parent's allMarkTypes */
-			oldrc->allMarkTypes |= newrc->allMarkTypes;
-
-			root->rowMarks = lappend(root->rowMarks, newrc);
+			Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+			childrte->inh = true;
+			appinfos = list_concat(appinfos,
+							   expand_inherited_rte_internal(root, childrte,
+										childRTindex, oldrc, lockmode, flatten));
 		}
 
-		/* Close child relations, but keep locks */
 		if (childOID != parentOID)
 			heap_close(newrelation, NoLock);
 	}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 	{
 		/* Clear flag before returning */
 		rte->inh = false;
-		return;
+		return NIL;
 	}
+	return appinfos;
+}
 
-	/* Otherwise, OK to add to root->append_rel_list */
-	root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ *		Process one child table in context of inheritance expansion for a
+ *		query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+						RangeTblEntry *parentRTE, Index parentRTindex,
+						Relation parentrel, Relation childrel,
+						PlanRowMark *parent_rc, bool inh,
+						RangeTblEntry **childRTE, Index *childRTindex)
+{
+	Query  *parse = root->parse;
+	Oid		parentOID = RelationGetRelid(parentrel),
+			childOID = RelationGetRelid(childrel);
+	RangeTblEntry  *newrte;
+	Index			newrti;
+	AppendRelInfo  *appinfo;
+
+	/*
+	 * Build an RTE for the child, and attach to query's rangetable list.
+	 * We copy most fields of the parent's RTE, but replace relation OID
+	 * and relkind, and set inh as requested.  Also, set requiredPerms to
+	 * zero since all required permissions checks are done on the original
+	 * RTE.
+	 */
+	newrte = copyObject(parentRTE);
+	newrte->relid = RelationGetRelid(childrel);
+	newrte->relkind = childrel->rd_rel->relkind;
+	newrte->inh = inh;
+	newrte->requiredPerms = 0;
+	parse->rtable = lappend(parse->rtable, newrte);
+	newrti = list_length(parse->rtable);
+
+	/* Return the child table RT entry and index if requested */
+	if (childRTE)
+		*childRTE = newrte;
+	if (childRTindex)
+		*childRTindex = newrti;
+
+	/*
+	 * Build an AppendRelInfo for this parent and child.
+	 */
+	appinfo = makeNode(AppendRelInfo);
+	appinfo->parent_relid = parentRTindex;
+	appinfo->child_relid = newrti;
+	appinfo->parent_reltype = parentrel->rd_rel->reltype;
+	appinfo->child_reltype = childrel->rd_rel->reltype;
+	make_inh_translation_list(parentrel, childrel, newrti,
+							  &appinfo->translated_vars);
+	appinfo->parent_reloid = parentOID;
+
+	/*
+	 * Translate the column permissions bitmaps to the child's attnums (we
+	 * have to build the translated_vars list before we can do this). But
+	 * if this is the parent table, leave copyObject's result alone.
+	 *
+	 * Note: we need to do this even though the executor won't run any
+	 * permissions checks on the child RTE.  The insertedCols/updatedCols
+	 * bitmaps may be examined for trigger-firing purposes.
+	 */
+	if (childOID != parentOID)
+	{
+		newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+											   appinfo->translated_vars);
+		newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+											   appinfo->translated_vars);
+		newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+											   appinfo->translated_vars);
+	}
+
+	/*
+	 * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+	 */
+	if (parent_rc)
+	{
+		PlanRowMark *newrc = makeNode(PlanRowMark);
+
+		newrc->rti = newrti;
+		newrc->prti = parentRTindex;
+		newrc->rowmarkId = parent_rc->rowmarkId;
+		/* Reselect rowmark type, because relkind might not match parent */
+		newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+		newrc->allMarkTypes = (1 << newrc->markType);
+		newrc->strength = parent_rc->strength;
+		newrc->waitPolicy = parent_rc->waitPolicy;
+		newrc->isParent = false;
+
+		/* Include child's rowmark type in parent's allMarkTypes */
+		parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+		root->rowMarks = lappend(root->rowMarks, newrc);
+	}
+
+	return appinfo;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
 	if (predicate_refuted_by(safe_restrictions, safe_restrictions))
 		return true;
 
-	/* Only plain relations have constraints */
-	if (rte->rtekind != RTE_RELATION || rte->inh)
+	/*
+	 * Only plain relations have constraints.  We represent a partitioned
+	 * table append member as its own append relation and hence would have
+	 * set rte->inh in that case.
+	 */
+	if (rte->rtekind != RTE_RELATION ||
+		(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return false;
 
 	/*
-- 
1.7.1


0006-Teach-a-few-places-to-use-partition-check-quals-10.patchtext/x-diff; name=0006-Teach-a-few-places-to-use-partition-check-quals-10.patchDownload
>From 7fae6d8cd6f3a29920457f5de89c493b21d74093 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   76 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 582 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..714b49c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row violates the partition boundary"
+							" specification of \"%s\"",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8ecc116..8036d3f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, false);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index df7cba6..8166f72 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row violates the partition boundary specification of "part_aa_bb"
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row violates the partition boundary specification of "part_ee_ff_1_10"
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..df6eb30 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row violates the partition boundary specification of "part_a_1_a_10"
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row violates the partition boundary specification of "part_b_10_b_20"
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..72c19e8 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1


0007-Introduce-a-PartitionTreeNode-data-structure-10.patchtext/x-diff; name=0007-Introduce-a-PartitionTreeNode-data-structure-10.patchDownload
>From 5fa74490588a22bb4393cdca02b4d1f8380d199f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  208 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 213 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3ec148b..fab418a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -924,6 +982,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1340,6 +1445,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 8df8454..e8da0fa 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
 extern List *get_leaf_partition_oids(Oid relid, int lockmode);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1


0008-Tuple-routing-for-partitioned-tables-10.patchtext/x-diff; name=0008-Tuple-routing-for-partitioned-tables-10.patchDownload
>From 468f996b86f0123fe7a2b5d09cfe1b7333031c10 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  327 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 +++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 +++++-
 src/backend/executor/nodeModifyTable.c  |  142 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   71 +++++++
 src/backend/optimizer/util/plancat.c    |   20 ++-
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 18 files changed, 926 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fab418a..e80e27f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1464,7 +1478,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1548,6 +1562,284 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be
+	 * less or equal with the tuple.  That is, the tuple belongs to the
+	 * partition with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+	 * is indeed marked !lower (that is, it's an upper bound).  If it turns
+	 * out that it is a lower bound then the corresponding index will be -1,
+	 * which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1815,6 +2107,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+	
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	if (cmpval == 0 && !bound->inclusive)
+		return bound->lower ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
  * Are two (consecutive) range bounds equal without distinguishing lower
  * and upper?
  */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1705,6 +1801,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2275,7 +2389,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2431,10 +2548,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2486,6 +2639,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2506,7 +2684,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2536,7 +2723,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2569,7 +2763,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc28393..7f34227 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1269,6 +1269,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 714b49c..e2853a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2997,3 +3002,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +537,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1597,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1688,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2097,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90d89d2..ec12ccb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9e7ae1f..a261c92 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 655aa1c..d047160 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..1fef64d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6159,6 +6159,77 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
-	/* Append partition predicates, if any */
+	/*
+	 * Append partition predicates, if any.  Note that we request the
+	 * parent's quals *not* to be included (by passing false) because if the
+	 * parent's quals cause it to be excluded, this relation will not be
+	 * processed in the first place.
+	 */
 	pcqual = RelationGetPartitionQual(relation, false);
 	if (pcqual)
 	{
@@ -1708,3 +1713,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index e8da0fa..165f24a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c62946f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..ce01008 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 89d5760..0f83bc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+ part_nulls       |    |  1
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1


0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-10.patchtext/x-diff; name=0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-10.patchDownload
>From 1a12495e49c35b99d07f9a5a7b5997a3c8948916 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..fe76ab0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1


#90Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

4. I'm somewhat wondering if we ought to just legislate that the lower
bound is always inclusive and the upper bound is always exclusive.
The decision to support both inclusive and exclusive partition bounds
is responsible for an enormous truckload of complexity in this patch,
and I have a feeling it is also going to be a not-infrequent cause of
user error.

I thought we decided at some point to go with range type like notation to
specify range partition bound because of its flexibility. I agree though
that with that flexibility, there will more input combinations that will
cause error. As for the internal complexity, it's not clear to me whether
it will be reduced by always-inclusive lower and always-exclusive upper
bounds. We would still need to store the inclusive flag with individual
PartitionRangeBound and consider it when comparing them with each other
and with partition key of tuples.

We did. But I now think that was kind of silly. I mean, we also
talked about not having a hard distinction between list partitioning
and range partitioning, but you didn't implement it that way and I
think that's probably a good thing. The question is - what's the
benefit of allowing this to be configurable?

For integers, there's absolutely no difference in expressive power.
If you want to allow 1000-2000 with both bounds inclusive, you can
just say START (1000) END (2001) instead of START (1000) END (2000)
INCLUSIVE. This is also true for any other datatype where it makes
sense to talk about "the next value" and "the previous value".
Instead of making the upper bound inclusive, you can just end at the
next value instead. If you were tempted to make the lower bound
exclusive, same thing.

For strings and numeric types that are not integers, there is in
theory a loss of power. If you want a partition that allows very
value starting with 'a' plus the string 'b' but not anything after
that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
done exactly what you want, but now you need to store the first string
that you *don't* want to include in that partition, and what's that?
Dunno. Or similarly if you want to store everything from 1.0 up to
and including 2.0 but nothing higher, you can't, really.

But who wants that? People who are doing prefix-based partitioning of
their text keys are going to want all of the 'a' things together, and
all of the 'b' things in another category. Same for ranges of
floating-point numbers, which are also probably an unlikely candidate
for a partitioning key anyway.

So let's look at the other side. What do we gain by excluding this
functionality? Well, we save a parser keyword: INCLUSIVE no longer
needs to be a keyword. We also can save some code in
make_one_range_bound(), RelationBuildPartitionDesc(),
copy_range_bound(), partition_rbound_cmp(), and partition_rbound_eq().

Also, if we exclude this now as I'm proposing, we can always add it
back later if it turns out that people need it. On the other hand, if
we include it in the first version, it's going to be very hard to get
rid of it if it turns out we don't want it. Once we release support
for a syntax, we're kinda stuck with it.

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

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

#91Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Reviewing 0006:

This patch seems scary. I sort of assumed from the title -- "Teach a
few places to use partition check quals." -- that this was an optional
thing, some kind of optimization from which we could reap further
advantage once the basic infrastructure was in place. But it's not
that at all. It's absolutely necessary that we do this, or data
integrity is fundamentally compromised. How do we know that we've
found all of the places that need to be taught about these new,
uncatalogued constraints?

I'm feeling fairly strongly like you should rewind and make the
partitioning constraints normal catalogued constraints. That's got a
number of advantages, most notably that we can be sure they will be
properly enforced by the entire system (modulo existing bugs, of
course). Also, they'll show up automatically in tools like psql's \d
output, pgAdmin, and anything else that is accustomed to being able to
find constraints in the catalog. We do need to make sure that those
constraints can't be dropped (or altered?) inappropriately, but that's
a relatively small problem. If we stick with the design you've got
here, every client tool in the world needs to be updated, and I'm not
seeing nearly enough advantage in this system to justify that kind of
upheaval.

In fact, as far as I can see, the only advantage of this approach is
that when the insert arrives through the parent and is routed to the
child by whatever tuple-routing code we end up with (I guess that's
what 0008 does), we get to skip checking the constraint, saving CPU
cycles. That's probably an important optimization, but I don't think
that putting the partitioning constraint in the catalog in any way
rules out the possibility of performing that optimization. It's just
that instead of having the partitioning excluded-by-default and then
sometimes choosing to include it, you'll have it included-by-default
and then sometimes choose to exclude it.

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

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

#92Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Insisting that you can't drop a child without detaching it first seems
wrong to me. If I already made this comment and you responded to it,
please point me back to whatever you said. However, my feeling is
this is flat wrong and absolutely must be changed.

I said the following [1]:

| Hmm, I don't think I like this. Why should it be necessary to detach
| a partition before dropping it? That seems like an unnecessary step.

I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.

That "OTOH" part seems like a pretty relevant point.

Basically, I think people expect to be able to say "DROP THINGTYPE
thingname" or at most "DROP THINGTYPE thingname CASCADE" and have that
thing go away. I'm opposed to anything which requires some other
series of steps without a very good reason, and possible surprise
about the precise locks that the command requires isn't a good enough
reason from my point of view.

I wonder if it's really a good idea for the partition constraints to
be implicit; what is the benefit of leaving those uncatalogued?

I did start out that way - ie, catalogued implicit constraints, but later
thought it might not be good to end up with multiple copies of essentially
the same information. With cataloguing them will come dependencies and
all places that know about pg_constraint.

In the long term, I think we're only going to need them because we want to
enforce them when directly inserting data into partitions.

See my other email on this topic. I agree there are some complexities
here, including making sure that pg_dump does the right thing. But I
think it's the right way to go.

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

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

#93Corey Huinker
corey.huinker@gmail.com
In reply to: Robert Haas (#90)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 12:57 PM, Robert Haas <robertmhaas@gmail.com> wrote:

For strings and numeric types that are not integers, there is in
theory a loss of power. If you want a partition that allows very
value starting with 'a' plus the string 'b' but not anything after
that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
done exactly what you want, but now you need to store the first string
that you *don't* want to include in that partition, and what's that?
Dunno. Or similarly if you want to store everything from 1.0 up to
and including 2.0 but nothing higher, you can't, really.

Exactly. This is especially true for date ranges. There's a lot of
cognitive dissonance in defining the "2014" partition as < '2015-01-01', as
was the case in Oracle waterfall-style partitioning. That was my reasoning
for pushing for range-ish syntax as well as form.

But who wants that? People who are doing prefix-based partitioning of
their text keys are going to want all of the 'a' things together, and
all of the 'b' things in another category. Same for ranges of
floating-point numbers, which are also probably an unlikely candidate
for a partitioning key anyway.

/me raises hand. We have tables with a taxonomy in them where the even
data splits don't fall on single letter boundaries, and often the single
string values have more rows than entire letters. In those situations,
being able to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let
'XYZ1' bleed into the partition and ['XYZ','XYZ1') lets in other values,
and so I go chasing down the non-discrete set rabbit hole.

If we're worried about keywords, maybe a BOUNDED '[]' clause?

#94Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Reviewing 0005:

Your proposed commit messages says this:

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).

In the immortal words of Frank Herbert: “I must not fear. Fear is the
mind-killer. Fear is the little-death that brings total obliteration.
I will face my fear. I will permit it to pass over me and through me.
And when it has gone past I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain.”

In other words, I'm not going to accept fear as a justification for
randomly-excluding the target-table case from this code. If there's
an actual reason not to do this in that case or some other case, then
let's document that reason. But weird warts in the code that are
justified only by nameless anxiety are not good.

Of course, the prior question is whether we should EVER be doing this.
I realize that something like this MAY be needed for partition-wise
join, but the mission of this patch is not to implement partition-wise
join. Does anything in this patch series really require this? If so,
what? If not, how about we leave it out and refactor it when that
change is really needed for something?

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

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

#95Robert Haas
robertmhaas@gmail.com
In reply to: Corey Huinker (#93)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 1:49 PM, Corey Huinker <corey.huinker@gmail.com> wrote:

Exactly. This is especially true for date ranges. There's a lot of cognitive
dissonance in defining the "2014" partition as < '2015-01-01', as was the
case in Oracle waterfall-style partitioning. That was my reasoning for
pushing for range-ish syntax as well as form.

Yeah. That syntax has some big advantages, though. If we define that
partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
INCLUSIVE, there's no way for the system to tell that the there's no
gap between the that ending bound and the starting bound of the 2015
partition, because the system has no domain-specific knowledge that
there is no daylight between 2014-12-31 and 2015-01-01. So if we
allow things to be specified that way, then people will use that
syntax and then complain when it doesn't perform quite as well as
START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
material and maybe we don't care; what do you think?

(I really don't want to get tied up adding a system for adding and
subtracting one to and from arbitrary data types. Life is too short.
If that requires that users cope with a bit of cognitive dissidence,
well, it's not the first time something like that will have happened.
I have some cognitive dissidence about the fact that creat(2) has no
trailing "e" but truncate(2) does, and moreover the latter can be used
to make a file longer rather than shorter. But, hey, that's what you
get for choosing a career in computer science.)

But who wants that? People who are doing prefix-based partitioning of
their text keys are going to want all of the 'a' things together, and
all of the 'b' things in another category. Same for ranges of
floating-point numbers, which are also probably an unlikely candidate
for a partitioning key anyway.

/me raises hand. We have tables with a taxonomy in them where the even data
splits don't fall on single letter boundaries, and often the single string
values have more rows than entire letters. In those situations, being able
to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
into the partition and ['XYZ','XYZ1') lets in other values, and so I go
chasing down the non-discrete set rabbit hole.

Hmm. I have to admit that I hadn't considered the case where you have
a range partitioning scheme but one of the ranges includes only a
single string. If that's an important use case, that might be a fatal
problem with my proposal. :-(

If we're worried about keywords, maybe a BOUNDED '[]' clause?

In the end, keywords are not the defining issue here; the issue is
whether all of this complexity around inclusive and exclusive bounds
carries its weight, and whether we want to be committed to that.

Any other opinions out there?

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

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

#96Francisco Olarte
folarte@peoplecall.com
In reply to: Corey Huinker (#93)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 6:49 PM, Corey Huinker <corey.huinker@gmail.com> wrote:

On Tue, Nov 1, 2016 at 12:57 PM, Robert Haas <robertmhaas@gmail.com> wrote:

For strings and numeric types that are not integers, there is in
theory a loss of power. If you want a partition that allows very
value starting with 'a' plus the string 'b' but not anything after
that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
done exactly what you want, but now you need to store the first string
that you *don't* want to include in that partition, and what's that?
Dunno. Or similarly if you want to store everything from 1.0 up to
and including 2.0 but nothing higher, you can't, really.

Exactly. This is especially true for date ranges. There's a lot of cognitive
dissonance in defining the "2014" partition as < '2015-01-01', as was the
case in Oracle waterfall-style partitioning. That was my reasoning for
pushing for range-ish syntax as well as form.

OTOH I've seen a lot of people bitten by [2014-01-01,2014-12-31] on
TIMESTAMP intervals.

Everybody remembers december has 31 days, but when we have to do
MONTHLY partitions if you use closed intervals someone always miskeys
the number of days, or forgets wheter a particular year is leap or
not, and when doing it automatically I always have to code it as start
+ 1 month - 1day. In my experience having the non-significant part of
the dates ( days in monthly case, months too in yearly cases ) both 1
and equal in start and end makes it easier to check and identify, and
less error prone.

But who wants that? People who are doing prefix-based partitioning of
their text keys are going to want all of the 'a' things together, and
all of the 'b' things in another category. Same for ranges of
floating-point numbers, which are also probably an unlikely candidate
for a partitioning key anyway.

/me raises hand. We have tables with a taxonomy in them where the even data
splits don't fall on single letter boundaries, and often the single string
values have more rows than entire letters. In those situations, being able
to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
into the partition and ['XYZ','XYZ1') lets in other values, and so I go
chasing down the non-discrete set rabbit hole.

You just do the classical ( I've had to do it ) closed end || minimum
char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
strings have a global order, the next string to any one is always that
plus the \0, or whatever your minimum is.

The problem is with anything similar to a real number, but then there
I've always opted for half-open interval, as they can cover the line
without overlapping, unlike closed ones.

Anyway, as long as anyone makes sure HALF-OPEN intervals are allowed,
I'm fine ( I do not remember the name, but once had to work with a
system that only allowed closed or open and it was a real PITA.

Francisco Olarte.

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

#97Robert Haas
robertmhaas@gmail.com
In reply to: Francisco Olarte (#96)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 2:05 PM, Francisco Olarte <folarte@peoplecall.com> wrote:

/me raises hand. We have tables with a taxonomy in them where the even data
splits don't fall on single letter boundaries, and often the single string
values have more rows than entire letters. In those situations, being able
to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
into the partition and ['XYZ','XYZ1') lets in other values, and so I go
chasing down the non-discrete set rabbit hole.

You just do the classical ( I've had to do it ) closed end || minimum
char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
strings have a global order, the next string to any one is always that
plus the \0, or whatever your minimum is.

In defense of Corey's position, that's not so easy. First, \0 doesn't
work; our strings can't include null bytes. Second, the minimum legal
character depends on the collation in use. It's not so easy to figure
out what the "next" string is, even though there necessarily must be
one.

The problem is with anything similar to a real number, but then there
I've always opted for half-open interval, as they can cover the line
without overlapping, unlike closed ones.

Anyway, as long as anyone makes sure HALF-OPEN intervals are allowed,
I'm fine ( I do not remember the name, but once had to work with a
system that only allowed closed or open and it was a real PITA.

I think we're all in agreement that half-open intervals should not
only be allowed, but the default. The question is whether it's a good
idea to also allow other possibilities.

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

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

#98Francisco Olarte
folarte@peoplecall.com
In reply to: Robert Haas (#95)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 7:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:

In the end, keywords are not the defining issue here; the issue is
whether all of this complexity around inclusive and exclusive bounds
carries its weight, and whether we want to be committed to that.

Any other opinions out there?

If it where for me I would opt for just half-open intervals. The only
problem I've ever had with them is when working with FINITE ranges,
i.e., there is no way of expresing the range of 8 bits integer with
half open intervals of 8 bit integers, but I would happily pay that
cost for the benefits of not having people unintentionally make
non-contiguous date/timestamp intervals, which I periodically suffer.

Francisco Olarte.

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

#99Corey Huinker
corey.huinker@gmail.com
In reply to: Robert Haas (#95)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 2:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Yeah. That syntax has some big advantages, though. If we define that
partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
INCLUSIVE, there's no way for the system to tell that the there's no
gap between the that ending bound and the starting bound of the 2015
partition, because the system has no domain-specific knowledge that
there is no daylight between 2014-12-31 and 2015-01-01. So if we
allow things to be specified that way, then people will use that
syntax and then complain when it doesn't perform quite as well as
START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
material and maybe we don't care; what do you think?

It was a fight I didn't expect to win, and even if we don't get
[x,x]-expressible partitions, at least we're not in the Oracle
context-waterfall, where the lower bound of your partition is determined by
the upper bound of the NEXT partition.

(I really don't want to get tied up adding a system for adding and

subtracting one to and from arbitrary data types. Life is too short.
If that requires that users cope with a bit of cognitive dissidence,
well, it's not the first time something like that will have happened.
I have some cognitive dissidence about the fact that creat(2) has no
trailing "e" but truncate(2) does, and moreover the latter can be used
to make a file longer rather than shorter. But, hey, that's what you
get for choosing a career in computer science.)

That noise your heard was the sound of my dream dying.

#100Corey Huinker
corey.huinker@gmail.com
In reply to: Francisco Olarte (#96)
Re: Declarative partitioning - another take

OTOH I've seen a lot of people bitten by [2014-01-01,2014-12-31] on
TIMESTAMP intervals.

No argument there.

Everybody remembers december has 31 days, but when we have to do
MONTHLY partitions if you use closed intervals someone always miskeys
the number of days, or forgets wheter a particular year is leap or
not, and when doing it automatically I always have to code it as start
+ 1 month - 1day. In my experience having the non-significant part of
the dates ( days in monthly case, months too in yearly cases ) both 1
and equal in start and end makes it easier to check and identify, and
less error prone.

Being able to define partitions by expressions based on the values I want
would be a good thing.

You just do the classical ( I've had to do it ) closed end || minimum
char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
strings have a global order, the next string to any one is always that
plus the \0, or whatever your minimum is.

I've thought about doing that in the past, but wasn't sure it would
actually work correctly. If you have experience with it doing so, that
would be encouraging. How does that *look* when you print your partition
layout though?

#101Francisco Olarte
folarte@peoplecall.com
In reply to: Robert Haas (#97)
Re: Declarative partitioning - another take

Robert:

On Tue, Nov 1, 2016 at 7:09 PM, Robert Haas <robertmhaas@gmail.com> wrote:

In defense of Corey's position, that's not so easy. First, \0 doesn't
work; our strings can't include null bytes. Second, the minimum legal
character depends on the collation in use. It's not so easy to figure
out what the "next" string is, even though there necessarily must be
one.

I'm aware of that, just wanted to point that it can be done on strings.

I think we're all in agreement that half-open intervals should not
only be allowed, but the default. The question is whether it's a good
idea to also allow other possibilities.

In my experience, people continuously misuse them. I would specially
like to have them disallowed on timestamp columns ( and other
real-like data, including numeric ). But knowing they cannot do a few
things, and some others are easier with them is enough for allowing
them as an explicit non default for me.

Francisco Olarte.

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

#102Robert Haas
robertmhaas@gmail.com
In reply to: Corey Huinker (#99)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 2:11 PM, Corey Huinker <corey.huinker@gmail.com> wrote:

On Tue, Nov 1, 2016 at 2:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Yeah. That syntax has some big advantages, though. If we define that
partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
INCLUSIVE, there's no way for the system to tell that the there's no
gap between the that ending bound and the starting bound of the 2015
partition, because the system has no domain-specific knowledge that
there is no daylight between 2014-12-31 and 2015-01-01. So if we
allow things to be specified that way, then people will use that
syntax and then complain when it doesn't perform quite as well as
START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
material and maybe we don't care; what do you think?

It was a fight I didn't expect to win, and even if we don't get
[x,x]-expressible partitions, at least we're not in the Oracle
context-waterfall, where the lower bound of your partition is determined by
the upper bound of the NEXT partition.

I certainly agree that was a horrible design decision on Oracle's
part. It's really messy. If you drop a partition, it changes the
partition constraint for the adjacent partition. Then you want to add
the partition back, say, but you have to first check whether the
adjacent partition, whose legal range has been expanded, has any
values that are out of bounds. Which it can't, but you don't know
that, so you have to scan the whole partition. Aargh! Maybe this
somehow works out in their system - I'm not an Oracle expert - but I
think it's absolutely vital that we don't replicate it into
PostgreSQL. (I have some, ahem, first-hand knowledge of how that
works out.)

(I really don't want to get tied up adding a system for adding and
subtracting one to and from arbitrary data types. Life is too short.
If that requires that users cope with a bit of cognitive dissidence,
well, it's not the first time something like that will have happened.
I have some cognitive dissidence about the fact that creat(2) has no
trailing "e" but truncate(2) does, and moreover the latter can be used
to make a file longer rather than shorter. But, hey, that's what you
get for choosing a career in computer science.)

That noise your heard was the sound of my dream dying.

Well, I'm not sure we've exactly reached consensus here, and you're
making me feel like I just kicked a puppy. However, my goal here is
not to kill your dream but to converge on a committable patch as
quickly as possible. Adding increment/decrement operators to every
discrete(-ish) type we have is not the way to accomplish that. To the
contrary, that's just about guaranteed to make this patch take an
extra release cycle to finish. Now, that does not necessarily mean we
can't keep the INCLUSIVE/EXCLUSIVE stuff -- after all, Amit has
already written it -- but then we have to live with the fact that
+1/-1 based optimizations and matching are not going to be there.
Whether it's still worth having fully-open and fully-closed ranges on
general principle -- and whether the lack of such optimizations
changes the calculus -- is what we are now debating.

More votes welcome. Right now I count 2 votes for keeping the
inclusive-exclusive stuff (Amit, Corey) and two for nuking it from
orbit (Robert, Francisco). I'm not going to put my foot down on this
point against a contrary consensus, so please chime in. Thanks.

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

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

#103Corey Huinker
corey.huinker@gmail.com
In reply to: Robert Haas (#102)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 2:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Well, I'm not sure we've exactly reached consensus here, and you're
making me feel like I just kicked a puppy.

It was hyperbole. I hope you found it as funny to read as I did to write.
This is a great feature and I'm not going to make "perfect" the enemy of
"good".

#104Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#94)
Re: Declarative partitioning - another take

On 2016/11/02 2:53, Robert Haas wrote:

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Reviewing 0005:

Your proposed commit messages says this:

If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).

I should have explained my thinking behind why I ended up not handling the
result relation case:

While set_append_rel_size() can be safely invoked recursively on the roots
of partition sub-trees, I was not quite sure if inheritance_planner() is
amenable to such recursive invocation. While the former is relatively
straightforward baserel processing, the latter is not-so-straightforward
transformation of the whole query for every target child relation of the
target parent.

In the immortal words of Frank Herbert: “I must not fear. Fear is the
mind-killer. Fear is the little-death that brings total obliteration.
I will face my fear. I will permit it to pass over me and through me.
And when it has gone past I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain.”

In other words, I'm not going to accept fear as a justification for
randomly-excluding the target-table case from this code. If there's
an actual reason not to do this in that case or some other case, then
let's document that reason. But weird warts in the code that are
justified only by nameless anxiety are not good.

Perhaps the above comment expands a little on the actual reason. Though I
guess it still amounts to giving up on doing a full analysis of whether
recursive processing within inheritance_planner() is feasible.

I think we could just skip this patch as long as a full investigation into
inheritance_planner() issues is not done. It's possible that there might
be other places in the planner code which are not amenable to the proposed
multi-level AppendRelInfos. Ashutosh had reported one [1]/messages/by-id/CAFjFpReZF34MDbY95xoATi0xVj2mAry4-LHBWVBayOc8gj=iqg@mail.gmail.com, wherein
lateral joins wouldn't work with multi-level partitioned table owing to
the multi-level AppendRelInfos (the patch contains a hunk to address that).

Of course, the prior question is whether we should EVER be doing this.
I realize that something like this MAY be needed for partition-wise
join, but the mission of this patch is not to implement partition-wise
join. Does anything in this patch series really require this? If so,
what? If not, how about we leave it out and refactor it when that
change is really needed for something?

Nothing *requires* it as such. One benefit I see is that exclusion of the
root of some partition sub-tree means the whole sub-tree is excluded in
one go. Currently, because of the flattening, every relation in that
sub-tree would be excluded separately, needlessly repeating the expensive
constraint exclusion proof. But I guess that's just an optimization.

Thanks,
Amit

[1]: /messages/by-id/CAFjFpReZF34MDbY95xoATi0xVj2mAry4-LHBWVBayOc8gj=iqg@mail.gmail.com
/messages/by-id/CAFjFpReZF34MDbY95xoATi0xVj2mAry4-LHBWVBayOc8gj=iqg@mail.gmail.com

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

#105Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#91)
Re: Declarative partitioning - another take

On 2016/11/02 2:34, Robert Haas wrote:

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Reviewing 0006:

Thanks for the review!

This patch seems scary. I sort of assumed from the title -- "Teach a
few places to use partition check quals." -- that this was an optional
thing, some kind of optimization from which we could reap further
advantage once the basic infrastructure was in place. But it's not
that at all. It's absolutely necessary that we do this, or data
integrity is fundamentally compromised. How do we know that we've
found all of the places that need to be taught about these new,
uncatalogued constraints?

Making this a separate commit from 0003 was essentially to avoid this
getting lost among all of its other changes. In fact, it was to bring to
notice for closer scrutiny whether all the sites in the backend code that
are critical for data integrity in face of the implicit partition
constraints are being informed about those constraints.

I'm feeling fairly strongly like you should rewind and make the
partitioning constraints normal catalogued constraints. That's got a
number of advantages, most notably that we can be sure they will be
properly enforced by the entire system (modulo existing bugs, of
course). Also, they'll show up automatically in tools like psql's \d
output, pgAdmin, and anything else that is accustomed to being able to
find constraints in the catalog. We do need to make sure that those
constraints can't be dropped (or altered?) inappropriately, but that's
a relatively small problem. If we stick with the design you've got
here, every client tool in the world needs to be updated, and I'm not
seeing nearly enough advantage in this system to justify that kind of
upheaval.

As for which parts of the system need to know about these implicit
partition constraints to *enforce* them for data integrity, we could say
that it's really just one site - ExecConstraints() called from
ExecInsert()/ExecUpdate().

Admittedly, the current error message style as in this patch exposes the
implicit constraint approach to a certain criticism: "ERROR: new row
violates the partition boundary specification of \"%s\"". It would say
the following if it were a named constraint: "ERROR: new row for relation
\"%s\" violates check constraint \"%s\""

For constraint exclusion optimization, we teach get_relation_constraints()
to look at these constraints. Although greatly useful, it's not the case
of being absolutely critical.

Beside the above two cases, there is bunch of code (relcache, DDL) that
looks at regular constraints, but IMHO, we need not let any of that code
concern itself with the implicit partition constraints. Especially, I
wonder why the client tools should want the implicit partitioning
constraint to be shown as a CHECK constraint? As the proposed patch 0004
(psql) currently does, isn't it better to instead show the partition
bounds as follows?

+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |     Modifiers
+--------+---------+--------------------
+ a      | text    |
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)

Needless to say, that could save a lot of trouble thinking about
generating collision-free names of these constraints, their dependency
handling, unintended altering of these constraints, pg_dump, etc.

In fact, as far as I can see, the only advantage of this approach is
that when the insert arrives through the parent and is routed to the
child by whatever tuple-routing code we end up with (I guess that's
what 0008 does), we get to skip checking the constraint, saving CPU
cycles. That's probably an important optimization, but I don't think
that putting the partitioning constraint in the catalog in any way
rules out the possibility of performing that optimization. It's just
that instead of having the partitioning excluded-by-default and then
sometimes choosing to include it, you'll have it included-by-default
and then sometimes choose to exclude it.

Hmm, doesn't it seem like we would be making *more* modifications to the
existing code (backend or otherwise) to teach it about excluding the
implicit partition constraints than the other way around? The other way
around being to modify the existing code to include the implicit
constraints which is what this patch is about.

Having said all that, I am open to switching to the catalogued partition
constraints if the arguments I make above in favor of this patch are not
all that sound.

Thanks,
Amit

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

#106Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#105)
Re: Declarative partitioning - another take

On 2016/11/02 16:41, Amit Langote wrote:

Having said all that, I am open to switching to the catalogued partition
constraints if the arguments I make above in favor of this patch are not
all that sound.

One problem I didn't immediately see a solution for if we go with the
catalogued partition constraints is how do we retrieve a relation's
partition constraint? There are a couple of cases where that becomes
necessary. Consider that we are adding a partition to a partitioned table
that is itself partition. In this case, the new partition's constraint
consists of whatever we generate from its FOR VALUES specification *ANDed*
with the parent's constraint. We must somehow get hold of the latter.
Which of the parent's named check constraints in the pg_constraint catalog
is supposed to be the partition constraint? With the uncatalogued
partition constraints approach, we simply generate it from the parent's
pg_class.relpartbound (or we might get the relcache copy of the same
stored in rd_partcheck). Hmm.

Thanks,
Amit

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

#107Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#92)
Re: Declarative partitioning - another take

On 2016/11/02 2:44, Robert Haas wrote:

On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Insisting that you can't drop a child without detaching it first seems
wrong to me. If I already made this comment and you responded to it,
please point me back to whatever you said. However, my feeling is
this is flat wrong and absolutely must be changed.

I said the following [1]:

| Hmm, I don't think I like this. Why should it be necessary to detach
| a partition before dropping it? That seems like an unnecessary step.

I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.

That "OTOH" part seems like a pretty relevant point.

Basically, I think people expect to be able to say "DROP THINGTYPE
thingname" or at most "DROP THINGTYPE thingname CASCADE" and have that
thing go away. I'm opposed to anything which requires some other
series of steps without a very good reason, and possible surprise
about the precise locks that the command requires isn't a good enough
reason from my point of view.

OK, I changed things so that DROP TABLE a_partition no longer complains
about requiring to detach first. Much like how index_drop() locks the
parent table ('parent' in a different sense, of course) and later
invalidates its relcache, heap_drop_with_catalog(), if the passed in relid
is a partition, locks the parent table using AccessExclusiveLock, performs
its usual business, and finally invalidates the parent's relcache before
closing it without relinquishing the lock. Does that sounds sane? One
downside is that if the specified command is DROP TABLE parent CASCADE,
the above described invalidation is a waste of cycles because the parent
will be dropped anyway after all the partitions are dropped.

Thanks,
Amit

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

#108Noname
alvherre@alvh.no-ip.org
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

El 2016-10-28 07:53, Amit Langote escribió:

@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, 
Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference relation \"%s\"", 
RelationGetRelationName(pkrel)),
+				 errdetail("Referencing partitioned tables in foreign key 
constraints is not supported.")));

Is there a plan for fixing this particular limitation? It's a pretty
serious problem for users,
and the suggested workaround (to create a separate non-partitioned table
which carries only the PK
columns which is updated by triggers, and direct the FKs to it instead
of to the partitioned table)
is not only a very ugly one, but also very slow.

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

#109Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#107)
Re: Declarative partitioning - another take

On Wed, Nov 2, 2016 at 6:47 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

OK, I changed things so that DROP TABLE a_partition no longer complains
about requiring to detach first. Much like how index_drop() locks the
parent table ('parent' in a different sense, of course) and later
invalidates its relcache, heap_drop_with_catalog(), if the passed in relid
is a partition, locks the parent table using AccessExclusiveLock, performs
its usual business, and finally invalidates the parent's relcache before
closing it without relinquishing the lock. Does that sounds sane?

Yes.

One
downside is that if the specified command is DROP TABLE parent CASCADE,
the above described invalidation is a waste of cycles because the parent
will be dropped anyway after all the partitions are dropped.

I don't think that's even slightly worth worrying about.

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

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

#110Robert Haas
robertmhaas@gmail.com
In reply to: Noname (#108)
Re: Declarative partitioning - another take

On Thu, Nov 3, 2016 at 7:46 AM, <alvherre@alvh.no-ip.org> wrote:

El 2016-10-28 07:53, Amit Langote escribió:

@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab,
Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+       if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("cannot reference relation
\"%s\"", RelationGetRelationName(pkrel)),
+                                errdetail("Referencing partitioned tables
in foreign key constraints is not supported.")));

Is there a plan for fixing this particular limitation? It's a pretty
serious problem for users,
and the suggested workaround (to create a separate non-partitioned table
which carries only the PK
columns which is updated by triggers, and direct the FKs to it instead of to
the partitioned table)
is not only a very ugly one, but also very slow.

If you have two compatibly partitioned tables, and the foreign key
matches the partitioning keys, you could implement a foreign key
between the two tables as a foreign key between each pair of matching
partitions. Otherwise, isn't the only way to handle this a global
index?

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

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

#111Robert Haas
robertmhaas@gmail.com
In reply to: Corey Huinker (#103)
Re: Declarative partitioning - another take

On Tue, Nov 1, 2016 at 2:36 PM, Corey Huinker <corey.huinker@gmail.com> wrote:

On Tue, Nov 1, 2016 at 2:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Well, I'm not sure we've exactly reached consensus here, and you're
making me feel like I just kicked a puppy.

It was hyperbole. I hope you found it as funny to read as I did to write.
This is a great feature and I'm not going to make "perfect" the enemy of
"good".

Oh, OK. Sorry, the tone did not transfer to my brain in the way you
intended it - I thought you were actually upset.

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

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

#112Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#105)
Re: Declarative partitioning - another take

On Wed, Nov 2, 2016 at 3:41 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

As for which parts of the system need to know about these implicit
partition constraints to *enforce* them for data integrity, we could say
that it's really just one site - ExecConstraints() called from
ExecInsert()/ExecUpdate().

Well, that's a slightly optimistic view of the situation, because the
issue is whether ExecConstraints() is going to get called in the first
place. But now that I look at it, there are only a handful of
callers, so maybe it's not so bad.

Admittedly, the current error message style as in this patch exposes the
implicit constraint approach to a certain criticism: "ERROR: new row
violates the partition boundary specification of \"%s\"". It would say
the following if it were a named constraint: "ERROR: new row for relation
\"%s\" violates check constraint \"%s\""

For constraint exclusion optimization, we teach get_relation_constraints()
to look at these constraints. Although greatly useful, it's not the case
of being absolutely critical.

Beside the above two cases, there is bunch of code (relcache, DDL) that
looks at regular constraints, but IMHO, we need not let any of that code
concern itself with the implicit partition constraints. Especially, I
wonder why the client tools should want the implicit partitioning
constraint to be shown as a CHECK constraint? As the proposed patch 0004
(psql) currently does, isn't it better to instead show the partition
bounds as follows?

+CREATE TABLE part_b PARTITION OF parted (
+       b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+       CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');

Good point.

+\d part_b
+         Table "public.part_b"
+ Column |  Type   |     Modifiers
+--------+---------+--------------------
+ a      | text    |
+ b      | integer | not null default 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)

Needless to say, that could save a lot of trouble thinking about
generating collision-free names of these constraints, their dependency
handling, unintended altering of these constraints, pg_dump, etc.

Well, that's certainly true, but those problems don't strike me as so
serious that they couldn't be solved with a reasonable amount of
labor.

In fact, as far as I can see, the only advantage of this approach is
that when the insert arrives through the parent and is routed to the
child by whatever tuple-routing code we end up with (I guess that's
what 0008 does), we get to skip checking the constraint, saving CPU
cycles. That's probably an important optimization, but I don't think
that putting the partitioning constraint in the catalog in any way
rules out the possibility of performing that optimization. It's just
that instead of having the partitioning excluded-by-default and then
sometimes choosing to include it, you'll have it included-by-default
and then sometimes choose to exclude it.

Hmm, doesn't it seem like we would be making *more* modifications to the
existing code (backend or otherwise) to teach it about excluding the
implicit partition constraints than the other way around? The other way
around being to modify the existing code to include the implicit
constraints which is what this patch is about.

I'm not sure which way is actually going to be more code modification,
but I guess you've persuaded me that the way you have it is
reasonable, so let's stick with that.

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

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

#113Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#89)
Re: Declarative partitioning - another take

Apologies if I've made some of these comments before and/or missed
comments you've made on these topics. The size of this patch set is
so large that it's hard to keep track of everything.

Re-reviewing 0001:

+ indicate which table columns are used as partition key. For example,

s/are used as/are part of the/

+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.

I think you can leave out "over the table columns".

+ columns or expressions forms the <firstterm>partitioning key</firstterm>

s/forms/form/

+ The table itself is empty. A data row inserted into the table is routed

s/The table/The partitioned table/

+     * Anything mentioned in the expressions.  We must ignore the column
+     * references which will count as self-dependency items; in this case,
+     * the depender is the table itself (there is no such thing as partition
+     * key object).

"depender" is not really a word, and the parenthetical note isn't very
clear. Maybe: We must ignore the column references, which will depend
on the table itself; there is no separate partition key object.

+ heap_close(pg_partitioned_table, RowExclusiveLock);

It seems like it would be safe to do this right after
CatalogUpdateIndexes(pg_partitioned_table, tuple), and I'd recommend
that you do. Not for performance or anything, but just to keep
related code together.

/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,

Perhaps we should rename this function to ResolveOpClass, since it's
now going to be used for both indexes and partitioning keys.

+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.

is_expr doesn't seem quite as clear as, say, used_by_expr or used_in_expr.

Also, the specification for this function doesn't seem to be very
clear about what this is supposed to do if the same column is both an
explicit partitioning column and also used in an expression, and the
code looks like it'll return with *is_expr set based on whichever use
it encounters first. If that's intended behavior, maybe add a comment
like: It's possible for a column to be used both directly and as part
of a partition key expression; if that happens, *is_expr may end up as
either true or false. That's OK for current uses of this function,
because *is_expr is only used to tailor the error message text.

+            if (is_expr)
+                *is_expr = false;
+            if (attnum == partattno)
+                return true;

I think you should adjust this (and the other bit in the same
function) so that you don't set *is_expr until you're committed to
returning.

+            index = -1;
+            while ((index = bms_next_member(expr_attrs, index)) > 0)
+            {
+                AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+                if (attno == attnum)
+                    return true;
+            }

How about bms_is_member(expr_attrs, attnum -
FirstLowInvalidHeapAttributeNumber), instead of looping?

+                 errmsg("cannot reference relation \"%s\"",
RelationGetRelationName(pkrel)),
+                 errdetail("Referencing partitioned tables in foreign
key constraints is not supported.")));

I think you could get rid of the errdetail and just have the error
message be "cannot reference partitioned table \"%s\"".

+ errmsg("column \"%s\" appears twice in
partition key", pelem->name),

It could be there three times! How about column \"%s\" appears more
than once in partition key? (I see that you seem to have adapted this
from some code in parse_utilcmd.c, which perhaps should also be
adjusted, but that's a job for another patch.)

+            /*
+             * Strip any top-level COLLATE clause.  This ensures that we treat
+             * "x COLLATE y" and "(x COLLATE y)" alike.
+             */

But you don't, right? Unless I am confused, which is possible, the
latter COLLATE will be ignored, while the former one will set the
collation to be used in the context of partitioning comparisons.

Re-reviewing 0002:

+       if (fout->remoteVersion >= 100000)
+       {
+               PQExpBuffer acl_subquery = createPQExpBuffer();
+               PQExpBuffer racl_subquery = createPQExpBuffer();
+               PQExpBuffer initacl_subquery = createPQExpBuffer();
+               PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+               PQExpBuffer attacl_subquery = createPQExpBuffer();
+               PQExpBuffer attracl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitracl_subquery = createPQExpBuffer();

It seems unnecessary to repeat all of this. The only differences
between the 10000 version and the 9600 version are:

60,61c60
< "AS changed_acl, "
< "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
---

"AS changed_acl "

73c72
< "WHERE c.relkind in ('%c', '%c', '%c', '%c',
'%c', '%c', '%c') "
---

"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "

87,88c86
< RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
< RELKIND_PARTITIONED_TABLE);
---

RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);

But none of that is really a problem. Sure, the 'P' case will never
arise in 9.6, but so what? I'd really like to not keep duplicating
these increasingly-complex hunks of code if we can find some way to
avoid that.

/* find all the inheritance information */

-       appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM
pg_inherits");
+       appendPQExpBufferStr(query,
+                                                "SELECT inhrelid, inhparent "
+                                                "FROM pg_inherits "
+                                                "WHERE inhparent NOT
IN (SELECT oid FROM pg_class WHERE relkind = 'P')");

I think you need to update the comment. "Find inheritance
information, excluding implicit inheritance via partitioning. We're
not interested in that case because $REASON."

Re-reviewing 0003:

+     <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.
+     </para>

This would, presumably, also be true for inheritance. I think we
could just leave this out.

+ as partition of the target table. The partition bound specification must

s/as partition/as a partition/

+ correspond to the partitioning method and partitioning key of the target

I think that in most places were are referring to the "partitioning
method" (with ing) but the "partition key" (without ing). Let's try to
be consistent.

+      table.  The table to be attached must have all the columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the matching constraints as the target table.

s/all the columns/all of the same columns/

The second sentence doesn't seem quite grammatical. And why would
that be true anyway? Partitions can have extra constraints, and if
they lack constraints that are present on the partitioned table, those
constraints will be added and validated, right?

+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the
<literal>VALIDATE</literal>
+      option were specified.

I don't think it's OK to allow the partition to be added if it
contains rows that might not be valid. We are generally vary wary
about adding options that can cause data integrity failures and I
think we shouldn't start here, either. On the other hand, it's also
not desirable for adding a partition to take O(n) time in all cases.
So what would be nice is if the new partition could self-certify that
contains no problematic rows by having a constraint that matches the
new partitioning constraint. Then we can skip the scan if that
constraint is already present.

+ inherited columns. One can also specify table constraints, in addition

Delete comma.

+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.

Doesn't that effectively delete the merged constraint?

Suggest: "If the parent already has a check constraint with the same
name as a constraint specified for the child, the conditions must be
the same."

+) FOR VALUES IN ('los angeles', 'san fransisco');

That's not you you spell San Francisco.

+   Create partition of a list partitioned table that itself is further
+   partitioned and then create its partition:

s/itself is/is itself/
s/then create its partition/then add a partition to it/

+                               if (!is_local || con->coninhcount == 0)
+                                       con->coninhcount++;

I would think that you could get rid of the "if" and just say
con->coninhcount = 1. It seems to me (and perhaps the comment could
say this) that for a partitioned table, we can simplify the handling
of coninhcount and conislocal. Since partitioning hierarchies don't
allow multiple inheritance, any inherited constraint must be inherited
exactly once. Since a partition cannot have inheritance children --
except by being partitioned itself -- there is no point in tracking
conislocal, so we always store it as false.

+
+void
+StorePartitionBound(Relation rel, Node *bound)

Header comment, please!

+       (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+                                                  &isnull);
+       Assert(isnull);

We try not to do unnecessary work in non-assert-enabled builds solely
for the benefit of assert-enabled builds. We also try not to end up
with variables that are only used in assert-enabled builds but not
marked PG_USED_FOR_ASSERTS_ONLY, because those tend to cause compiler
warnings. I'm not sure an compiler would be smart enough to warn
about this, but I suggest adding an #ifdef USE_ASSERT_CHECKING with a
block inside where the offending variable is declared. Actually, I
think you need to move a bit more code. Hmm. Something like:

#ifdef USE_ASSERT_CHECKING
{
Form_pg_class classForm;
bool isnull;

classForm = (Form_pg_class) GETSTRUCT(tuple);
Assert(!classForm->relispartition);

(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
&isnull);
Assert(isnull);
}
#endif

+ * are same in common cases, of which we only store one.

"and we only store one of them."

+                                                       /*
+                                                        * Never put a
null into the values array, flag
+                                                        * instead for
the code further down below where
+                                                        * we
construct the actual relcache struct.
+                                                        */
+
found_null_partition = true;
+
null_partition_index = i;

How about we elog(ERROR, ...) if found_null_partition is already set?

+                               foreach(cell, non_null_values)
+                               {
+                                       PartitionListValue      *src =
lfirst(cell);
+
+                                       all_values[i] = (PartitionListValue *)
+
         palloc(sizeof(PartitionListValue));
+                                       all_values[i]->value =
datumCopy(src->value,
+
                                  key->parttypbyval[0],
+
                                  key->parttyplen[0]);
+                                       all_values[i]->index = src->index;
+                                       i++;
+                               }

Why do we need to datumCopy() here when we just did that in the
previous loop? Why did the previous loop need to datumCopy(), anyway?
Can't we just pass the same datums around? I understand that you
need to copy the data into the correct context at the end, but doing
two copies prior to that seems excessive.

+get_leaf_partition_oids(Oid relid, int lockmode)

Instead of implementing this, can you just use find_all_inheritors()?

+               /*
+                * Is lower_val = upper_val?
+                */
+               if (lower_val && upper_val)

So, that comment does not actually match that code. That if-test is
just checking whether both bounds are finite. What I think you should
be explaining here is that if lower_val and upper_val are both
non-infinite, and if the happen to be equal, we want to emit an
equality test rather than a >= test plus a <= test because $REASON.

+       operoid = get_opfamily_member(key->partopfamily[col],
+
key->parttypid[col],
+
key->parttypid[col],
+                                                                 strategy);
+       if (!OidIsValid(operoid))
+       {
+               operoid = get_opfamily_member(key->partopfamily[col],
+
   key->partopcintype[col],
+
   key->partopcintype[col],
+
   strategy);
+               *need_relabel = true;
+       }

There's a comment explaining THAT you do this ("Use either the column
type as the operator datatype or opclass's declared input type.") but,
to repeat a complaint that I've made often, nothing explaining why.
In this particular case, what's not clear - in my opinion - is why you
need to try two different possibilities, and why at least one of those
possibilities is guaranteed to work. I gather that if the opfamily
doesn't contain an operator for the actual type of the partitioning
column, you think it will certainly contain one for the input type of
the operator class (which seems right), and that the input type of the
operator class will be binary-compatible with the type of the
partitioning column (which is less-obviously true, and needs some
explanation).

I also think that this function should elog(ERROR, ...) if by any
chance the second get_opfamily_member() call also fails. Otherwise the
error might occur quite a bit downstream and be hard to understand.

+       ReleaseSysCache(tuple);
+       heap_close(parent, AccessShareLock);

I think you had better not release the lock here - i.e. pass NoLock.
We don't release locks on tables until transaction commit, except for
catalog tables. This also comes up in, at least,
transformPartitionOf().

+    PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+    PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+       PartitionKey key = (PartitionKey) arg;
+
+       return partition_rbound_cmp(key, b1, b2);

Whitespace.

+ * as partition and schema consists of columns definitions corresponding

the schema consists

-               if (recurse)
+               /* Force inheritance recursion, if partitioned table. */
+               if (recurse || rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE)

I would instead error out if ONLY is specified for a partitioned table.

+               /*
+                * If the table is source table of ATTACH PARTITION
command, following
+                * check is unnecessary.
+                */

As usual, comment should say WHY.

+                       if (partqualstate && !ExecQual(partqualstate,
econtext, true))
+                               ereport(ERROR,
+
(errcode(ERRCODE_CHECK_VIOLATION),
+                                                errmsg("child table
contains a row violating partition bound specification")));

Why not mimic the existing phrasing? "partition constraint is
violated by some row"

What happens if you try to attach a table as a partition of itself or
one of its ancestors?

-                               errmsg("column \"%s\" in child table
must be marked NOT NULL",
-                                          attributeName)));
+                                                errmsg("column \"%s\"
in child table must be marked NOT NULL",
+
attributeName)));

Whitespace-only hunk; revert. You cannot fight the power of pgindent.

+ errmsg("cannot attach table that is a
inheritance child as partition")));

an inheritance child

+ errmsg("cannot attach a temporary relation of another
session as partition ")));

Extra space.

+                                        errdetail("Table being
attached should contain only the columns"
+                                                          " present
in parent.")));

Suggest: "New partition should contain only..."

Also, don't break error messages into multiple strings. Make it one
long string and let pgindent deal.

+                       | FOR VALUES START '(' range_datum_list ')' lb_inc
+                                                END_P '('
range_datum_list ')' ub_inc

Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
idiomatic than START and END?

+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);
+

If there's anyway that we might do heap_openrv() on the same RangeVar
at multiple places in the code, it presents security and integrity
hazards because the referent of that RangeVar might change in the
meantime. I suspect there is such a risk here - won't we need to open
the relation again when we actually want to do the operation?

Also, we should never acquire a lower-level lock on a relation and
then, further downstream, acquire a higher-level lock on the same
object. To do so creates a deadlock hazard. That seems like it might
be a problem here, too.

+                               /*if (ldatum->infinite && rdatum->infinite)
+                                       ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                                errmsg("both START
and END datum for column \"%s\" cannot be UNBOUNDED",
+                                                               colname),
+
parser_errposition(cxt->pstate, rdatum->location)));*/

Commented-out code is bad.

+                       if (list_length(spec->lowerdatums) > partnatts)
+                               ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("START has more values
than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
  list_length(spec->lowerdatums) - 1)))));
+                       else if (list_length(spec->lowerdatums) < partnatts)
+                               ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("START has fewer
values than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
  list_length(spec->lowerdatums) - 1)))));

It would be worth looking for a way to unify these cases. Like "START
must specify exactly one value per partitioning column".

+                * Same oids? No mind order - in the list case, it
matches the order
+                * in which partition oids are returned by a
pg_inherits scan, whereas
+                * in the range case, they are in order of ranges of individual
+                * partitions.  XXX - is the former unsafe?

Probably. It likely depends on the physical ordering of tuples in the
table, which can change.

+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.

"physical or logical relations" is unfamiliar terminology.

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

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

#114Jaime Casanova
jaime.casanova@2ndquadrant.com
In reply to: Amit Langote (#89)
2 attachment(s)
Re: Declarative partitioning - another take

On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Please find attached the latest version of the patches

Hi,

I started to review the functionality of this patch, so i applied all
9 patches. After that i found this warning, which i guess is because
it needs a cast.

After that, i tried a case that i think is important: to partition an
already existing table. Because there is no ALTER TABL SET PARTITION
or something similar (which i think makes sense because such a command
would need to create the partitions and move the rows to follow the
rule that there is no rows in a parent table).

So, what i tried was:

1) rename original table
2) create a new partitioned table with old name
3) attach old table as a partition with bounds outside normal bounds
and no validate

the idea is to start attaching valid partitions and delete and insert
rows from the invalid one (is there a better way of doing that?), that
will allow to partition a table easily.
So far so good, until i decided points 1 to 3 should happen inside a
transaction to make things transparent to the user.

Attached is script that shows the failure when trying it:

script 1 (failing_test_1.sql) fails the assert
"Assert(RelationGetPartitionKey(parentRel) != NULL);" in
transformAttachPartition() at src/backend/parser/parse_utilcmd.c:3164

After that i tried the same but with an already partitioned (via
inheritance) table and got this (i did this first without a
transaction, doing it with a transaction will show the same failure as
before):

script 2 (failing_test_2.sql) fails the assert
"Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);" in
expand_inherited_rte_internal() at
src/backend/optimizer/prep/prepunion.c:1551

PS: i don't like the START END syntax but i don't have any ideas
there... except, there was a reason not to use expressions (like a
CHECK constraint?)

--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

failing_test_1.sqlapplication/sql; name=failing_test_1.sqlDownload
failing_test_2.sqlapplication/sql; name=failing_test_2.sqlDownload
#115Jaime Casanova
jaime.casanova@2ndquadrant.com
In reply to: Jaime Casanova (#114)
1 attachment(s)
Re: Declarative partitioning - another take

On 7 November 2016 at 12:15, Jaime Casanova
<jaime.casanova@2ndquadrant.com> wrote:

On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Please find attached the latest version of the patches

Hi,

I started to review the functionality of this patch, so i applied all
9 patches. After that i found this warning, which i guess is because
it needs a cast.

oh! i forgot the warning
"""
partition.c: In function ‘get_qual_for_list’:
partition.c:1159:6: warning: assignment from incompatible pointer type
or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
^
"""

attached a list of the warnings that my compiler give me (basically
most are just variables that could be used uninitialized)

--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

warnings.txttext/plain; charset=UTF-8; name=warnings.txtDownload
#116Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jaime Casanova (#115)
Re: Declarative partitioning - another take

Hi Jaime,

On 2016/11/08 2:24, Jaime Casanova wrote:

On 7 November 2016 at 12:15, Jaime Casanova
<jaime.casanova@2ndquadrant.com> wrote:

On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Please find attached the latest version of the patches

Hi,

I started to review the functionality of this patch, so i applied all
9 patches. After that i found this warning, which i guess is because
it needs a cast.

oh! i forgot the warning
"""
partition.c: In function ‘get_qual_for_list’:
partition.c:1159:6: warning: assignment from incompatible pointer type
or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
^
"""

This one I noticed too and have fixed.

attached a list of the warnings that my compiler give me (basically
most are just variables that could be used uninitialized)

Thanks a lot for spotting and reporting these. Will fix as appropriate.

Regards,
Amit

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

#117Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jaime Casanova (#114)
Re: Declarative partitioning - another take

Hi Jaime,

On 2016/11/08 2:15, Jaime Casanova wrote:

On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

I started to review the functionality of this patch, so i applied all
9 patches. After that i found this warning, which i guess is because
it needs a cast.

Thanks a ton for reviewing!

After that, i tried a case that i think is important: to partition an
already existing table. Because there is no ALTER TABL SET PARTITION
or something similar (which i think makes sense because such a command
would need to create the partitions and move the rows to follow the
rule that there is no rows in a parent table).

So, what i tried was:

1) rename original table
2) create a new partitioned table with old name
3) attach old table as a partition with bounds outside normal bounds
and no validate

the idea is to start attaching valid partitions and delete and insert
rows from the invalid one (is there a better way of doing that?), that
will allow to partition a table easily.

So, step 3 creates a partition that is basically unbounded. From there,
it seems you want to create partitions with proper bounds, moving data
into them as they are created. It seems like a job of some redistribution
command (split partition?) which is currently unsupported.

So far so good, until i decided points 1 to 3 should happen inside a
transaction to make things transparent to the user.

Attached is script that shows the failure when trying it:

script 1 (failing_test_1.sql) fails the assert
"Assert(RelationGetPartitionKey(parentRel) != NULL);" in
transformAttachPartition() at src/backend/parser/parse_utilcmd.c:3164

Thanks for the test. This test uncovered a bug which I have fixed in my
local repository. Given the fix the above will work, although I see that
it's not the best way to do what you want to do.

After that i tried the same but with an already partitioned (via
inheritance) table and got this (i did this first without a
transaction, doing it with a transaction will show the same failure as
before):

script 2 (failing_test_2.sql) fails the assert
"Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);" in
expand_inherited_rte_internal() at
src/backend/optimizer/prep/prepunion.c:1551

This again was an oversight/bug in the patch. It's not supported to
combine old-style inheritance partitioning with the new partitioned
tables. In fact, ATTACH PARTITION prevents adding a regular inheritance
parent as partition. After fixing the bug, you would instead get this error:

alter table prueba attach partition prueba_old for values start
(unbounded, unbounded) end (2008,1) no validate;
ERROR: cannot attach regular inheritance parent as partition

In this case, you should instead create prueba_old partition hierarchy
using the new partitioning commands and then attach the same.

PS: i don't like the START END syntax but i don't have any ideas
there... except, there was a reason not to use expressions (like a
CHECK constraint?)

Expression syntax is too unrestricted. What's to prevent users from using
completely different check constraint expressions from partition to
partition (not that any users deliberately try do that)? With the new
partitioning syntax, you specify the partitioning columns once when
creating the partitioned parent and then specify, using partitioning
method specific syntax, the bounds for every partition of the parent.
That allows to capture the partition metadata in a form that is more
suitable to implement other features related to partitioning.

That said, I think it's always a challenge to come up with a syntax that
is universally acceptable. For example, the recent discussion about
whether to allow inclusive/exclusive to be specified for START and END
bounds of range partitions or always assume inclusive start and exclusive
end [1]/messages/by-id/CA+TgmoaKOycHcVoed+F3fk-z6xUOeysQFG6HT=oucw76bSMHCQ@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoaKOycHcVoed+F3fk-z6xUOeysQFG6HT=oucw76bSMHCQ@mail.gmail.com
/messages/by-id/CA+TgmoaKOycHcVoed+F3fk-z6xUOeysQFG6HT=oucw76bSMHCQ@mail.gmail.com

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

#118Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#113)
8 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/04 9:16, Robert Haas wrote:

Apologies if I've made some of these comments before and/or missed
comments you've made on these topics. The size of this patch set is
so large that it's hard to keep track of everything.

Thanks for the reviews!

Re-reviewing 0001:

+ indicate which table columns are used as partition key. For example,

s/are used as/are part of the/

Fixed.

+       third table columns make up the partition key.  A zero in this array
+       indicates that the corresponding partition key column is an expression
+       over the table columns, rather than a simple column reference.

I think you can leave out "over the table columns".

Done.

+ columns or expressions forms the <firstterm>partitioning key</firstterm>

s/forms/form/

Actually the sentence there is: The parenthesized list of columns or
expressions forms the the <firstterm>partitioning key</firstterm> for the
table

+ The table itself is empty. A data row inserted into the table is routed

s/The table/The partitioned table/

Done.

+     * Anything mentioned in the expressions.  We must ignore the column
+     * references which will count as self-dependency items; in this case,
+     * the depender is the table itself (there is no such thing as partition
+     * key object).

"depender" is not really a word, and the parenthetical note isn't very
clear. Maybe: We must ignore the column references, which will depend
on the table itself; there is no separate partition key object.

I see "depender" being used as a variable name, but I guess it's not
appropriate to use the same in comments. In any case, I adopted your text.

+ heap_close(pg_partitioned_table, RowExclusiveLock);

It seems like it would be safe to do this right after
CatalogUpdateIndexes(pg_partitioned_table, tuple), and I'd recommend
that you do. Not for performance or anything, but just to keep
related code together.

I see, done.

/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,

Perhaps we should rename this function to ResolveOpClass, since it's
now going to be used for both indexes and partitioning keys.

Aha, good idea! Done.

+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.

is_expr doesn't seem quite as clear as, say, used_by_expr or used_in_expr.

Also, the specification for this function doesn't seem to be very
clear about what this is supposed to do if the same column is both an
explicit partitioning column and also used in an expression, and the
code looks like it'll return with *is_expr set based on whichever use
it encounters first. If that's intended behavior, maybe add a comment
like: It's possible for a column to be used both directly and as part
of a partition key expression; if that happens, *is_expr may end up as
either true or false. That's OK for current uses of this function,
because *is_expr is only used to tailor the error message text.

OK, I added the note as you suggest.

+            if (is_expr)
+                *is_expr = false;
+            if (attnum == partattno)
+                return true;

I think you should adjust this (and the other bit in the same
function) so that you don't set *is_expr until you're committed to
returning.

Done.

+            index = -1;
+            while ((index = bms_next_member(expr_attrs, index)) > 0)
+            {
+                AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+                if (attno == attnum)
+                    return true;
+            }

How about bms_is_member(expr_attrs, attnum -
FirstLowInvalidHeapAttributeNumber), instead of looping?

Done that way, thanks.

+                 errmsg("cannot reference relation \"%s\"",
RelationGetRelationName(pkrel)),
+                 errdetail("Referencing partitioned tables in foreign
key constraints is not supported.")));

I think you could get rid of the errdetail and just have the error
message be "cannot reference partitioned table \"%s\"".

OK, done.

+ errmsg("column \"%s\" appears twice in
partition key", pelem->name),

It could be there three times! How about column \"%s\" appears more
than once in partition key? (I see that you seem to have adapted this
from some code in parse_utilcmd.c, which perhaps should also be
adjusted, but that's a job for another patch.)

Done.

+            /*
+             * Strip any top-level COLLATE clause.  This ensures that we treat
+             * "x COLLATE y" and "(x COLLATE y)" alike.
+             */

But you don't, right? Unless I am confused, which is possible, the
latter COLLATE will be ignored, while the former one will set the
collation to be used in the context of partitioning comparisons.

The code immediately following the comment does in fact strip the
top-level COLLATE clause.

while (IsA(expr, CollateExpr))
expr = (Node *) ((CollateExpr *) expr)->arg;

So that the following two specifications are equivalent which is the intent:

create table p (a text) partition by range (a collate "en_US");
vs.
create table p (a text) partition by range ((a collate "en_US"));

Re-reviewing 0002:

+       if (fout->remoteVersion >= 100000)
+       {
+               PQExpBuffer acl_subquery = createPQExpBuffer();
+               PQExpBuffer racl_subquery = createPQExpBuffer();
+               PQExpBuffer initacl_subquery = createPQExpBuffer();
+               PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+               PQExpBuffer attacl_subquery = createPQExpBuffer();
+               PQExpBuffer attracl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitracl_subquery = createPQExpBuffer();

It seems unnecessary to repeat all of this. The only differences
between the 10000 version and the 9600 version are:

60,61c60
< "AS changed_acl, "
< "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
---

"AS changed_acl "

73c72
< "WHERE c.relkind in ('%c', '%c', '%c', '%c',
'%c', '%c', '%c') "
---

"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "

87,88c86
< RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
< RELKIND_PARTITIONED_TABLE);
---

RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);

But none of that is really a problem. Sure, the 'P' case will never
arise in 9.6, but so what? I'd really like to not keep duplicating
these increasingly-complex hunks of code if we can find some way to
avoid that.

We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
getTables() sends to pre-10 servers, right? But I suppose we need not
call it in that particular SQL query in the first place.

How about we do it in the following manner in getSchemaData():

if (g_verbose)
write_msg(NULL, "reading partition key information for interesting
tables\n");
getTablePartitionKeyInfo(fout, tblinfo, numTables);

I have implemented the same.

/* find all the inheritance information */

-       appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM
pg_inherits");
+       appendPQExpBufferStr(query,
+                                                "SELECT inhrelid, inhparent "
+                                                "FROM pg_inherits "
+                                                "WHERE inhparent NOT
IN (SELECT oid FROM pg_class WHERE relkind = 'P')");

I think you need to update the comment. "Find inheritance
information, excluding implicit inheritance via partitioning. We're
not interested in that case because $REASON."

I just realized that this hunk really belongs in patch 0004. In any case,
I added the explanatory comment like you suggest.

Re-reviewing 0003:

+     <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.
+     </para>

This would, presumably, also be true for inheritance. I think we
could just leave this out.

Actually, it isn't true for the regular inheritance situation.

create table parent (a int not null);
create table child () inherits (parent);
alter table child alter a drop not null; -- this works (bug?)

vs.

create table p (a int, b int, c int) partition by range (a);
create table p1 partition of p for values start (1) end (10);
alter table p1 alter a drop not null; -- this causes error

Once NOT NULL constraints start using pg_constraint [1]/messages/by-id/16589.1465997664@sss.pgh.pa.us to keep track of
inheritance (which currently is not kept track of), the above illustrated
bug will be fixed. If that patch gets committed, it will have taken care
of the partitioning case as well.

But in the meantime, we can proceed with enforcing inheritance on NOT NULL
constraint for *partitions*, because they only ever have one parent and
hence do not need elaborate coninhcount based scheme, I think. Thoughts?

+ as partition of the target table. The partition bound specification must

s/as partition/as a partition/

Fixed.

+ correspond to the partitioning method and partitioning key of the target

I think that in most places were are referring to the "partitioning
method" (with ing) but the "partition key" (without ing). Let's try to
be consistent.

I'm inclined to switch to "partitioning method" and "partitioning key",
but do you mean just the documentation or throughout? Beside
documentation, I mean source code comments, error messages, etc. I have
assumed throughout.

+      table.  The table to be attached must have all the columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the matching constraints as the target table.

s/all the columns/all of the same columns/

Fixed.

The second sentence doesn't seem quite grammatical. And why would
that be true anyway? Partitions can have extra constraints, and if
they lack constraints that are present on the partitioned table, those
constraints will be added and validated, right?

It's true that partitions can have extra constraints which don't affect
the discussion. What I meant to say with the second sentence is that any
check constraints defined on the parent table (being referred to as the
target table) must be present in the table being attached. It's the same
rule as for regular inheritance. I didn't change how that works in the
partitioning case. So no, check constraints of the parent table that are
missing in the table being attached are not added and validated as part of
the attach partition command processing.

I did change the second sentence to: Also, it must have all the
<literal>NOT NULL</literal> and <literal>CHECK</literal> constraints
present in the target table.

+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the
<literal>VALIDATE</literal>
+      option were specified.

I don't think it's OK to allow the partition to be added if it
contains rows that might not be valid. We are generally vary wary
about adding options that can cause data integrity failures and I
think we shouldn't start here, either. On the other hand, it's also
not desirable for adding a partition to take O(n) time in all cases.
So what would be nice is if the new partition could self-certify that
contains no problematic rows by having a constraint that matches the
new partitioning constraint. Then we can skip the scan if that
constraint is already present.

I agree that NO VALIDATE is undesirable and it would be better if we could
get rid of the same. I want to clarify one thing though: what does it
mean for the new partition to have a constraint that *matches* the new
partitioning constraint? Does it mean the new partition's constraint
*implies* the partitioning constraint? Or as certified by equal()?

+ inherited columns. One can also specify table constraints, in addition

Delete comma.

Done.

+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.

Doesn't that effectively delete the merged constraint?

Suggest: "If the parent already has a check constraint with the same
name as a constraint specified for the child, the conditions must be
the same."

+) FOR VALUES IN ('los angeles', 'san fransisco');

That's not you you spell San Francisco.

Oops. I changed the example such that specified values are properly
capitalized.

<programlisting>
CREATE TABLE cities (
name text not null,
population int,
) PARTITION BY LIST (initcap(name));
</programlisting></para>

<programlisting>
CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');
</programlisting></para>

Previously, the key was: PARTITION BY LIST (lower(name))

+   Create partition of a list partitioned table that itself is further
+   partitioned and then create its partition:

s/itself is/is itself/
s/then create its partition/then add a partition to it/

Fixed.

+                               if (!is_local || con->coninhcount == 0)
+                                       con->coninhcount++;

I would think that you could get rid of the "if" and just say
con->coninhcount = 1.

That's better. So:

/*
* In case of partitions, an inherited constraint must be
* inherited only once since it cannot have multiple parents and
* it is never considered local.
*/
if (rel->rd_rel->relispartition)
{
con->coninhcount = 1;
con->conislocal = false;
}

The above is enforced in both MergeWithExistingConstraint() and
MergeConstraintsIntoExisting().

It seems to me (and perhaps the comment could
say this) that for a partitioned table, we can simplify the handling
of coninhcount and conislocal. Since partitioning hierarchies don't
allow multiple inheritance, any inherited constraint must be inherited
exactly once. Since a partition cannot have inheritance children --
except by being partitioned itself -- there is no point in tracking
conislocal, so we always store it as false.

Agreed.

+
+void
+StorePartitionBound(Relation rel, Node *bound)

Header comment, please!

Sorry about that, added.

+       (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+                                                  &isnull);
+       Assert(isnull);

We try not to do unnecessary work in non-assert-enabled builds solely
for the benefit of assert-enabled builds. We also try not to end up
with variables that are only used in assert-enabled builds but not
marked PG_USED_FOR_ASSERTS_ONLY, because those tend to cause compiler
warnings. I'm not sure an compiler would be smart enough to warn
about this, but I suggest adding an #ifdef USE_ASSERT_CHECKING with a
block inside where the offending variable is declared. Actually, I
think you need to move a bit more code. Hmm. Something like:

#ifdef USE_ASSERT_CHECKING
{
Form_pg_class classForm;
bool isnull;

classForm = (Form_pg_class) GETSTRUCT(tuple);
Assert(!classForm->relispartition);

(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
&isnull);
Assert(isnull);
}
#endif

Done this way, thanks.

+ * are same in common cases, of which we only store one.

"and we only store one of them."

Done.

+                                                       /*
+                                                        * Never put a
null into the values array, flag
+                                                        * instead for
the code further down below where
+                                                        * we
construct the actual relcache struct.
+                                                        */
+
found_null_partition = true;
+
null_partition_index = i;

How about we elog(ERROR, ...) if found_null_partition is already set?

Makes sense. However, let me mention here that duplicates either within
one partition's list or across the partitions are not possible. That's
because in case of the former, we de-duplicate before storing the list
into the catalog and the latter would simply be an overlap error. Could
this be made an Assert() then?

+                               foreach(cell, non_null_values)
+                               {
+                                       PartitionListValue      *src =
lfirst(cell);
+
+                                       all_values[i] = (PartitionListValue *)
+
palloc(sizeof(PartitionListValue));
+                                       all_values[i]->value =
datumCopy(src->value,
+
key->parttypbyval[0],
+
key->parttyplen[0]);
+                                       all_values[i]->index = src->index;
+                                       i++;
+                               }

Why do we need to datumCopy() here when we just did that in the
previous loop? Why did the previous loop need to datumCopy(), anyway?
Can't we just pass the same datums around? I understand that you
need to copy the data into the correct context at the end, but doing
two copies prior to that seems excessive.

Agreed. Now the datumCopying() happens only once when copying to the
relcache context.

+get_leaf_partition_oids(Oid relid, int lockmode)

Instead of implementing this, can you just use find_all_inheritors()?

OK, got rid of get_leaf_partition_oids().

+               /*
+                * Is lower_val = upper_val?
+                */
+               if (lower_val && upper_val)

So, that comment does not actually match that code. That if-test is
just checking whether both bounds are finite. What I think you should
be explaining here is that if lower_val and upper_val are both
non-infinite, and if the happen to be equal, we want to emit an
equality test rather than a >= test plus a <= test because $REASON.

OK, I have added some explanatory comments around this code.

+       operoid = get_opfamily_member(key->partopfamily[col],
+
key->parttypid[col],
+
key->parttypid[col],
+                                                                 strategy);
+       if (!OidIsValid(operoid))
+       {
+               operoid = get_opfamily_member(key->partopfamily[col],
+
key->partopcintype[col],
+
key->partopcintype[col],
+
strategy);
+               *need_relabel = true;
+       }

There's a comment explaining THAT you do this ("Use either the column
type as the operator datatype or opclass's declared input type.") but,
to repeat a complaint that I've made often, nothing explaining why.
In this particular case, what's not clear - in my opinion - is why you
need to try two different possibilities, and why at least one of those
possibilities is guaranteed to work. I gather that if the opfamily
doesn't contain an operator for the actual type of the partitioning
column, you think it will certainly contain one for the input type of
the operator class (which seems right), and that the input type of the
operator class will be binary-compatible with the type of the
partitioning column (which is less-obviously true, and needs some
explanation).

Sorry, it is indeed not clear why the code is the way it it. I expanded
the comment explaining why (borrowing from what you wrote above).

I also think that this function should elog(ERROR, ...) if by any
chance the second get_opfamily_member() call also fails. Otherwise the
error might occur quite a bit downstream and be hard to understand.

OK, added an elog(ERROR, ...) after the second get_opfamily_member() call.

+       ReleaseSysCache(tuple);
+       heap_close(parent, AccessShareLock);

I think you had better not release the lock here - i.e. pass NoLock.
We don't release locks on tables until transaction commit, except for
catalog tables. This also comes up in, at least,
transformPartitionOf().

Ah, fixed.

+    PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+    PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+       PartitionKey key = (PartitionKey) arg;
+
+       return partition_rbound_cmp(key, b1, b2);

Whitespace.

Fixed.

+ * as partition and schema consists of columns definitions corresponding

the schema consists

-               if (recurse)
+               /* Force inheritance recursion, if partitioned table. */
+               if (recurse || rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE)

I would instead error out if ONLY is specified for a partitioned table.

I forgot this instance of forced-recursion-if-partitioned, fixed. I was
not quite sure what the error message would say; how about:

ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("must truncate child tables too")));

+               /*
+                * If the table is source table of ATTACH PARTITION
command, following
+                * check is unnecessary.
+                */

As usual, comment should say WHY.

OK, expanded the comment explaining why.

+                       if (partqualstate && !ExecQual(partqualstate,
econtext, true))
+                               ereport(ERROR,
+
(errcode(ERRCODE_CHECK_VIOLATION),
+                                                errmsg("child table
contains a row violating partition bound specification")));

Why not mimic the existing phrasing? "partition constraint is
violated by some row"

Agreed, done.

What happens if you try to attach a table as a partition of itself or
one of its ancestors?

The latter fails with "already a partition" error.

The former case was not being handled at all which has now been fixed.
ATExecAddInherit() prevents that case as an instance of preventing the
circularity of inheritance. It says: ERROR: circular inheritance not
allowed. And then: DETAIL: "rel" is already a child of "rel".

Should ATExecAttachPartition() use the same trick and keep using the same
message(s)? The above detail message doesn't quite sound appropriate when
one tries to attach a table as partition of itself.

-                               errmsg("column \"%s\" in child table
must be marked NOT NULL",
-                                          attributeName)));
+                                                errmsg("column \"%s\"
in child table must be marked NOT NULL",
+
attributeName)));

Whitespace-only hunk; revert. You cannot fight the power of pgindent.

Oops, fixed.

+ errmsg("cannot attach table that is a
inheritance child as partition")));

an inheritance child

+ errmsg("cannot attach a temporary relation of another
session as partition ")));

Extra space.

Fixed.

+                                        errdetail("Table being
attached should contain only the columns"
+                                                          " present
in parent.")));

Suggest: "New partition should contain only..."

Also, don't break error messages into multiple strings. Make it one
long string and let pgindent deal.

Done.

+                       | FOR VALUES START '(' range_datum_list ')' lb_inc
+                                                END_P '('
range_datum_list ')' ub_inc

Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
idiomatic than START and END?

It would actually. Should I go ahead and change to FROM (...) TO (...)?

Related to range partitioning, should we finalize on inclusive START/FROM
and exclusive END/TO preventing explicit specification of the inclusivity?

+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);
+

If there's anyway that we might do heap_openrv() on the same RangeVar
at multiple places in the code, it presents security and integrity
hazards because the referent of that RangeVar might change in the
meantime. I suspect there is such a risk here - won't we need to open
the relation again when we actually want to do the operation?

Also, we should never acquire a lower-level lock on a relation and
then, further downstream, acquire a higher-level lock on the same
object. To do so creates a deadlock hazard. That seems like it might
be a problem here, too.

I think I have modified things so that the concerns you express are taken
care of. In particular, I got rid of transformPartitionOf() altogether,
moving part of its responsibilities to MergeAttributes() where the parent
table is locked anyway and calling transformPartitionBound from
DefineRelation() (we needed access to the parent's PartitionKey for that).

+                               /*if (ldatum->infinite && rdatum->infinite)
+                                       ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                                errmsg("both START
and END datum for column \"%s\" cannot be UNBOUNDED",
+                                                               colname),
+
parser_errposition(cxt->pstate, rdatum->location)));*/

Commented-out code is bad.

Oops, removed.

+                       if (list_length(spec->lowerdatums) > partnatts)
+                               ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("START has more values
than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
list_length(spec->lowerdatums) - 1)))));
+                       else if (list_length(spec->lowerdatums) < partnatts)
+                               ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("START has fewer
values than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
list_length(spec->lowerdatums) - 1)))));

It would be worth looking for a way to unify these cases. Like "START
must specify exactly one value per partitioning column".

Agreed. The code is trying to generate too specific error messages.

+                * Same oids? No mind order - in the list case, it
matches the order
+                * in which partition oids are returned by a
pg_inherits scan, whereas
+                * in the range case, they are in order of ranges of individual
+                * partitions.  XXX - is the former unsafe?

Probably. It likely depends on the physical ordering of tuples in the
table, which can change.

This comment is outdated. RelationBuildPartitionDesc() now always puts
the OIDs into the array in a canonical order (in both list and range
cases) using. I have updated the comment.

+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations.  It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.

"physical or logical relations" is unfamiliar terminology.

What I am trying to say there is that one can associate a BoundCollection
with either an actual partitioned table or with a transient partitioned
relation, say, a partitioned joinrel. In case of the former, the
BoundCollection is part of the table's partition descriptor. I can see
though that "logical/physical relations" terminology is misleading at
best. I rewrote the comment:

/*
* BoundCollection encapsulates a set of partition bounds. It is usually
* associated with partitioned tables as part of its partition descriptor.
*
* The internal structure is opaque outside partition.c.
*/
typedef struct BoundCollectionData *BoundCollection;

Attached updated patches take care of the above comments and few other
fixes. There are still a few items I have not addressed right away:

- Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
new partition's constraints to skip the validation scan
- Remove the syntax to specify inclusivity of each of START and END bounds
of range partitions and instead assume inclusive START and exclusive END

Also, what used to be "[PATCH 5/9] Refactor optimizer's inheritance set
expansion code" is no longer included in the set. I think we can go back
to that topic later as I mentioned last week [2]/messages/by-id/0a299fbf-e5a1-dc3d-eb5e-11d3601eac16@lab.ntt.co.jp.

Thanks,
Amit

[1]: /messages/by-id/16589.1465997664@sss.pgh.pa.us
[2]: /messages/by-id/0a299fbf-e5a1-dc3d-eb5e-11d3601eac16@lab.ntt.co.jp
/messages/by-id/0a299fbf-e5a1-dc3d-eb5e-11d3601eac16@lab.ntt.co.jp

Attachments:

0007-Tuple-routing-for-partitioned-tables-11.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-11.patchDownload
From 5a7933f07ce8a9a4eea2a1b4ca603926c8e09ad2 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  327 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 +++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 +++++-
 src/backend/executor/nodeModifyTable.c  |  142 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   77 +++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 18 files changed, 926 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 69c3e52..9cba603 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1449,7 +1463,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1533,6 +1547,284 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be
+	 * less or equal with the tuple.  That is, the tuple belongs to the
+	 * partition with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+	 * is indeed marked !lower (that is, it's an upper bound).  If it turns
+	 * out that it is a lower bound then the corresponding index will be -1,
+	 * which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1798,6 +2090,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	if (cmpval == 0 && !bound->inclusive)
+		return bound->lower ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
  * Are two (consecutive) range bounds equal without distinguishing lower
  * and upper?
  */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1705,6 +1801,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2275,7 +2389,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2431,10 +2548,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2486,6 +2639,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2506,7 +2684,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2536,7 +2723,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2569,7 +2763,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index de19e9c..88b9d1f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1292,6 +1292,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ea3f59a..62bf8b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +537,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1597,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1688,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2097,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c0d48d0..ca48547 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d5eb0cc..7a219e5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 655aa1c..d047160 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			/*
+			 * We are only interested in foreign tables.  Note that this will
+			 * also eliminate any partitioned tables since foreign tables can
+			 * only ever be leaf partitions.
+			 */
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index ab6d3a8..09727cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -56,4 +58,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c62946f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..ce01008 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ebc0ba5..3d97742 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_nulls       |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 4bf042e..d1b5a09 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-11.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-11.patchDownload
From 2ecc0409bb3f3067f7812a7bf8440759e3e9a9ad Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..fe76ab0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

0001-Catalog-and-DDL-for-partitioned-tables-11.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-11.patchDownload
From 9d53813d2f1e8ac1ab0d6f57c56a40cc00d55a87 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  165 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 51 files changed, 1947 insertions(+), 54 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..255fdde 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partitioning key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partitioning key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partitioning key.
+       For example, a value of <literal>1 3</literal> would mean that the
+       first and the third table columns make up the partitioning key.  A zero
+       in this array indicates that the corresponding partitioning key column
+       is an expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partitioning key, this contains the OID of
+       the operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partitioning key, this contains the OID of
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partitioning key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partitioning key
+       columns are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..5b2a867 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partitioning key</firstterm>
+      for the table.  When using range partitioning, the partitioning key can
+      include multiple columns or expressions, but for list partitioning, the
+      partitioning key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partitioning key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..232a432 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,137 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partitioning key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partitioning key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partitioning key of relation %u", relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..96358bb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partitioning key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..82588a8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partitioning key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partitioning key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partitioning key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partitioning key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partitioning key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partitioning key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partitioning key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partitioning key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partitioning key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partitioning key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partitioning key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partitioning key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partitioning key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partitioning key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..76c0793 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partitioning key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..0627256 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partitioning key expression");
+			else
+				err = _("grouping operations are not allowed in partitioning key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partitioning key expression");
+			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 63f7965..3facde2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partitioning key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..41b85c3 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partitioning key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..eaf809a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partitioning key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partitioning key information.  That means no complicated
+ * logic to free individual elements whenever the relcache entry is flushed -
+ * just delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..a1a62bb
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partitioning key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partitioning key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9b600a5..a04dbeb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partitioning key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partitioning key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1768,6 +1796,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..6e1ec82 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partitioning key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c867ebb..e751491 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partitioning key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partitioning key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partitioning key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partitioning key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -533,6 +565,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..c39fac5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partitioning key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partitioning key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partitioning key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partitioning key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..0d70cc9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partitioning key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partitioning key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partitioning key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partitioning key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partitioning key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partitioning key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partitioning key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partitioning key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partitioning key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partitioning key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partitioning key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-11.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-11.patchDownload
From 2964345f52bfb8edf98b5eeaaab8781d5e1c669f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..319531c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partitioning key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partitioning key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partitioning key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c12c490 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partitioning key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c393264 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4960,7 +4964,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partitioning key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partitioning key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a60cf95..a75e79c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partitioning key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..6d44378 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partitioning key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partitioning key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..5e49475 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partitioning key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0d70cc9..bec5655 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partitioning key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partitioning key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-11.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-11.patchDownload
From 7b1324c79b421bb08d6800e10a2ca9a2d2f9f9d0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  100 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   26 +
 doc/src/sgml/ref/create_table.sgml         |   92 ++-
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |  105 ++-
 src/backend/catalog/partition.c            | 1685 ++++++++++++++++++++++++++++
 src/backend/commands/createas.c            |    2 +-
 src/backend/commands/sequence.c            |    2 +-
 src/backend/commands/tablecmds.c           |  897 +++++++++++++---
 src/backend/commands/typecmds.c            |    3 +-
 src/backend/commands/view.c                |    3 +-
 src/backend/nodes/copyfuncs.c              |   50 +
 src/backend/nodes/equalfuncs.c             |   44 +
 src/backend/nodes/nodeFuncs.c              |    6 +
 src/backend/nodes/outfuncs.c               |   29 +
 src/backend/nodes/readfuncs.c              |   36 +
 src/backend/parser/gram.y                  |  235 ++++-
 src/backend/parser/parse_utilcmd.c         |  252 +++++-
 src/backend/tcop/utility.c                 |    6 +-
 src/backend/utils/cache/relcache.c         |   93 ++-
 src/include/catalog/heap.h                 |    1 +
 src/include/catalog/partition.h            |   54 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/commands/tablecmds.h           |    2 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   52 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/parser/parse_utilcmd.h         |    2 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  237 ++++
 src/test/regress/expected/create_table.out |  190 ++++
 src/test/regress/sql/alter_table.sql       |  198 ++++
 src/test/regress/sql/create_table.sql      |  153 +++
 34 files changed, 4458 insertions(+), 165 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 255fdde..b1ddb12 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..9ae16ac 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,47 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as a partition of the target table.  The partition bound specification
+      must correspond to the partitioning method and partitioning key of the
+      target table.  The table to be attached must have all the same columns
+      as the target table and no more; moreover, the column types must also
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints present in the target table.
+      If some <literal>CHECK</literal> constraint of the table being attached
+      is marked <literal>NO INHERIT</literal>, the command will fail; such
+      constraints must be recreated without the <literal>NO INHERIT</literal>
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+      option were specified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +769,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +986,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1044,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 specified.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1119,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1308,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..f05f212 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES 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 5b2a867..e3a434c 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | START ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] END ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +247,50 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partitioning key of the parent table, and must not overlap
+      with any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES 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 Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES START (10000) END (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 232a432..062841b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3175,3 +3223,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..3ae8cf6
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1685 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; has
+						 * same length as the values array above */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;			/* range bound datums */
+	bool   *infinite;		/* infinite flag for each datum */
+	bool	inclusive;		/* bound is inclusive (vs exclusive) */
+	bool	lower;			/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, and we only store one of them.
+ */
+typedef struct PartitionRangeInfo
+{
+	PartitionRangeBound **bounds;
+	int		nbounds;
+	int	   *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+	char	strategy;	/* list or range bounds? */
+
+	union
+	{
+		struct PartitionListInfo	lists;
+		struct PartitionRangeInfo	ranges;
+	} bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	Datum	value;
+	int		index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums,
+					 bool inclusive, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+												  PartitionRangeBound *,
+												  void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	PartitionListValue **all_values = NULL;
+	int			all_values_count = 0;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	PartitionRangeBound **distinct_bounds = NULL;
+	int			num_distinct_bounds = 0;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.  XXX - perhaps it would be better if we
+		 * did not need to use the parent relation desc in that case, because
+		 * the following looks kind of ugly.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		ReleaseSysCache(tuple);
+		partoids = lappend_oid(partoids, inhrelid);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null_partition = false;
+				null_partition_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null_partition)
+								elog(ERROR, "found null more than once");
+							found_null_partition = true;
+							null_partition_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				all_values_count = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **)
+								  palloc(all_values_count *
+										sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, all_values_count,
+						  sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp,
+						  (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+					lower = make_one_range_bound(key, i,
+												 spec->lowerdatums,
+												 spec->lowerinc, true);
+					/*
+					 * In most cases, upper bound of the  i'th partition is
+					 * equal in *value* to the lower bound of the (i+1)'th
+					 * partition, execpt they have different inclusivity.
+					 * We want to keep only the former in the final list of
+					 * bounds by eliminating the latter.  To be able to equate
+					 * the two, we flip the inclusivity flag of the latter,
+					 * so that it matches the former's flag value in exactly
+					 * the cases where the elimination must occur.
+					 */
+					lower->inclusive = !lower->inclusive;
+
+					upper = make_one_range_bound(key, i,
+												 spec->upperdatums,
+												 spec->upperinc, false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				num_distinct_bounds = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.
+					 */
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						num_distinct_bounds++;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				distinct_bounds = (PartitionRangeBound **)
+											palloc0(num_distinct_bounds *
+												sizeof(PartitionRangeBound *));
+				k = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						distinct_bounds[k++] = cur;
+
+					/*
+					 * If it's a lower bound, flip the inclusive flag to its
+					 * original value.
+					 */
+					if (cur->lower)
+						cur->inclusive = !cur->inclusive;
+
+					prev = cur;
+				}
+				Assert(k == num_distinct_bounds);
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->boundinfo = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		result->boundinfo->strategy = key->strategy;
+
+		/* Allocate and initialize with invalid mapping values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionListInfo	listinfo;
+
+				listinfo.nvalues = all_values_count;
+				listinfo.has_null = found_null_partition;
+				listinfo.values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo.indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they match for any two list partitioned tables
+				 * with same number of partitions and same lists per partition.
+				 * One way to achieve this is to assign indexes in the same
+				 * order as the least values of the individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo.values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo.indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo.null_index = mapping[null_partition_index];
+				else
+					listinfo.null_index = -1;
+
+				result->boundinfo->bounds.lists = listinfo;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeInfo	rangeinfo;
+
+				rangeinfo.bounds = (PartitionRangeBound **)
+										palloc(num_distinct_bounds *
+											sizeof(PartitionRangeBound *));
+				rangeinfo.indexes = (int *)
+										palloc((num_distinct_bounds + 1) *
+													sizeof(int));
+
+				for (i = 0; i < num_distinct_bounds; i++)
+				{
+					rangeinfo.bounds[i] =
+								copy_range_bound(key, distinct_bounds[i]);
+
+					/* There is no mapping for invalid indexes (ie, -1) */
+					if (rangeinfo.bounds[i]->lower)
+						rangeinfo.indexes[i] = -1;
+					else
+					{
+						int		orig_index = rangeinfo.bounds[i]->index;
+
+						/* If this index has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						rangeinfo.indexes[i] = mapping[orig_index];
+					}
+				}
+				rangeinfo.indexes[i] = -1;
+				rangeinfo.nbounds = num_distinct_bounds;
+
+				result->boundinfo->bounds.ranges = rangeinfo;
+				break;
+			}
+		}
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds (ie, sorted).
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2)
+{
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key,
+								 &b1->bounds.lists, &b2->bounds.lists))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key,
+								  &b1->bounds.ranges, &b2->bounds.ranges))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	Relation		parent;
+	PartitionKey	key;
+	PartitionDesc	pdesc;
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	parent = heap_open(parentId, NoLock); /* already locked */
+	key = RelationGetPartitionKey(parent);
+	pdesc = RelationGetPartitionDesc(parent);
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (pdesc->nparts > 0)
+			{
+				ListCell   *cell;
+				bool		new_has_null = false;
+				PartitionListInfo	listinfo;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+				listinfo = pdesc->boundinfo->bounds.lists;
+				Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+				/* If the new partition's list contains null, set a flag. */
+				foreach (cell, spec->listdatums)
+				{
+					Const *val = lfirst(cell);
+
+					if (val->constisnull)
+					{
+						new_has_null = true;
+						break;
+					}
+				}
+
+				/*
+				 * Now check against listinfo whether the new partition's
+				 * values are not already taken by existing partitions.
+				 */
+				if (new_has_null && listinfo.has_null)
+				{
+					with = listinfo.null_index;
+					overlap = true;
+				}
+				else
+				{
+					foreach (cell, spec->listdatums)
+					{
+						Const  *val = lfirst(cell);
+						int		found = -1;
+
+						/* bsearch a new list's value in listinfo->values */
+						if (!val->constisnull)
+							found = partition_list_values_bsearch(key,
+														listinfo.values,
+														listinfo.nvalues,
+														val->constvalue);
+						if (found >= 0)
+						{
+							overlap = true;
+							with = listinfo.indexes[found];
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1,
+										 spec->lowerdatums, spec->lowerinc,
+										 true);
+			upper = make_one_range_bound(key, -1,
+										 spec->upperdatums, spec->upperinc,
+										 false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified bounds
+			 */
+			if (partition_rbound_cmp(key, lower, upper) > 0 ||
+				(partition_rbound_cmp(key, lower, upper) == 0
+					&& (!spec->lowerinc || !spec->upperinc)))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				PartitionRangeInfo rangeinfo;
+				int		  offset1, offset2;
+				bool	  equal = false;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+				rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than the new lower bound.
+				 */
+				offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+											  rangeinfo.nbounds, lower,
+											  partition_rbound_cmp, false, &equal);
+
+				/*
+				 * If equal has been set to true, that means the new bound is
+				 * found to be equal to some existing bound.  offset1 == -1
+				 * means that all existing range bounds are greater than the
+				 * new bound.  If offset >= 0, and there exists a gap between
+				 * the corresponding bound and the next, new partition can
+				 * still manage to fit within the same. To confirm whether it
+				 * is so, check the new upper bound.
+				 */
+				if (!equal &&
+					(offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+				{
+					offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+												  rangeinfo.nbounds, upper,
+												  partition_rbound_cmp, false,
+												  &equal);
+
+					if (equal || offset1 != offset2)
+					{
+						overlap = true;
+						with = rangeinfo.indexes[offset2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					if (offset1 == -1)
+					{
+						Assert(equal);
+						offset1 = 0;
+					}
+					with = rangeinfo.indexes[offset1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(pdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference in there*/
+	Assert(!found_whole_row);
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If the list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, remove the null Const from the list and create a IS NULL
+	 * test and create an OR expression along with ScalarArrayOpExpr. We do
+	 * it this way because the null-valued expression does not have the
+	 * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key emitting an OpExpr for each, based
+	 * on the corresponding elements of the lower and upper datums.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *key_col;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		NullTest	   *nulltest;
+		Oid			operoid;
+		uint16		strategy;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * If either of lower or upper datum is infinite, stop at this column,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (key_col = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create a test expression to check if lower_val = upper_val */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					key_col = (Node *) makeRelabelType((Expr *) key_col,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) key_col,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val <> upper_val.  Emit expressions
+		 * (key_col >/>= lower_val) and (key_col </<= upper_val), provided
+		 * lower_val and upper_val are finite, respectively.
+		 */
+		if (lower_val)
+		{
+			strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+										: BTGreaterStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+											: BTLessStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator, in the same
+	 * opreator family, with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			value1 = (*(const PartitionListValue **) a)->value,
+					value2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = partition_list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums,
+					 bool inclusive, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+	bound->inclusive = inclusive;
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		bound->infinite[i] = datum->infinite;
+
+		if (!bound->infinite[i])
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot specify NULL in range bound")));
+			else
+				bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	result->index = src->index;
+	result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+	result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+	result->inclusive = src->inclusive;
+	result->lower = src->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		result->infinite[i] = src->infinite[i];
+		if (!result->infinite[i])
+			result->datums[i] = datumCopy(src->datums[i],
+										  key->parttypbyval[i],
+										  key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	int		i;
+
+	if (r1->nbounds != r2->nbounds)
+		return false;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < r1->nbounds; i++)
+	{
+		if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+			return false;
+
+		if (r1->indexes[i] != r2->indexes[i])
+			return false;
+	}
+
+	/* There is one more index than bounds */
+	if (r1->indexes[i] != r2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+	PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int32	cmpval;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (b1->infinite[i] && b2->infinite[i])
+		{
+			/*
+			 * Both are infinity, so they are equal unless one is lower and
+			 * the other not.
+			 */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? -1 : 1;
+		}
+		else if (b1->infinite[i])
+			return b1->lower ? -1 : 1;
+		else if (b2->infinite[i])
+			return b2->lower ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.
+	 */
+	if (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		int32	cmpval = 0;
+
+		/*
+		 * If either of them has infinite element, we can't equate them.
+		 * Even if both were infinite, they'd have opposite signs (it always
+		 * holds that b1 and b2 are lower and upper bound or vice versa).
+		 */
+		if (b1->infinite[i] || b2->infinite[i])
+			return false;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			return false;
+	}
+
+	/*
+	 * All datums are found to be equal, now only if both are inclusive
+	 * or exclusive then they are considered equal.
+	 */
+	if (b1->inclusive == b2->inclusive)
+		return true;
+
+	return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple.  The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal)
+{
+	int		lo, hi, mid;
+
+	lo = -1;
+	hi = n - 1;
+	while (lo < hi)
+	{
+		PartitionRangeBound *cur;
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cur = bounds[mid];
+
+		cmpval = cmp(key, cur, probe);
+		if (is_equal)
+			*is_equal = (cmpval == 0);
+
+		if (cmpval < 0 || (equal_allowed && cmpval == 0))
+			lo = mid;
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 82588a8..de19e9c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -163,6 +164,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 */
+	List	   *partition_quals; /* partition check quals for attach partition
+								  * validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -346,7 +350,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +450,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +477,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,7 +608,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	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
@@ -619,6 +631,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +743,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parentRel;
+
+		/* Already have strong enough lock on the parent */
+		parentRel = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partitioning key of parentRel, so it better have one.
+		 */
+		if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parentRel))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+		heap_close(parentRel, NoLock);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parentId, bound);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partitioning key information into the catalog.
@@ -1096,6 +1167,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
+		/* Force inheritance recursion, if partitioned table. */
 		if (recurse)
 		{
 			ListCell   *child;
@@ -1117,6 +1189,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1474,7 +1550,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;
@@ -1484,6 +1561,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1508,53 +1586,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * Although we might consider merging such entries in the same way that we
 	 * handle name conflicts for inherited attributes, it seems to make more
 	 * sense to assume such conflicts are errors.
+	 *
+	 * In case of partitions, schema contains only the WITH OPTION entries
+	 * at this point, so the following checks are meaningless yet.
 	 */
-	foreach(entry, schema)
+	if (!is_partition)
 	{
-		ColumnDef  *coldef = lfirst(entry);
-		ListCell   *rest = lnext(entry);
-		ListCell   *prev = entry;
-
-		if (coldef->typeName == NULL)
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
 
 			/*
 			 * 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.
 			 */
-			ereport(ERROR,
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" does not exist",
 							coldef->colname)));
 
-		while (rest != NULL)
-		{
-			ColumnDef  *restdef = lfirst(rest);
-			ListCell   *next = lnext(rest);		/* need to save it in case we
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);	/* need to save it in case we
 												 * delete it */
 
-			if (strcmp(coldef->colname, restdef->colname) == 0)
-			{
-				if (coldef->is_from_type)
+				if (strcmp(coldef->colname, restdef->colname) == 0)
 				{
-					/*
-					 * merge the column options into the column from the type
-					 */
-					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_from_type = false;
-					list_delete_cell(schema, rest, prev);
-				}
-				else
-					ereport(ERROR,
-							(errcode(ERRCODE_DUPLICATE_COLUMN),
+					if (coldef->is_from_type)
+					{
+						/*
+						 * merge the column options into the column from the
+						 * type
+						 */
+						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_from_type = false;
+						list_delete_cell(schema, rest, prev);
+					}
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_DUPLICATE_COLUMN),
 							 errmsg("column \"%s\" specified more than once",
 									coldef->colname)));
+				}
+				prev = rest;
+				rest = next;
 			}
-			prev = rest;
-			rest = next;
 		}
 	}
 
@@ -1582,18 +1666,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1706,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1716,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1855,6 +1962,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		heap_close(relation, NoLock);
 	}
 
+	/* In case of a partition, there are no new column definitions */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
 	/*
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
@@ -1886,6 +2000,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				Oid			defcollid,
 							newcollid;
 
+				Assert(!is_partition);
+
 				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
@@ -1934,7 +2050,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(newdef->storage))));
 
-				/* Mark the column as locally defined */
+				/* Mark the column as locally defined (unless partition) */
 				def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
 				def->is_not_null |= newdef->is_not_null;
@@ -1968,6 +2084,52 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have a column definition list for a partition, we can check
+	 * whether the referenced column actually exists.
+	 */
+	if (is_partition)
+		schema = list_concat(schema, saved_schema);
+
+	foreach(entry, schema)
+	{
+		ColumnDef  *coldef = lfirst(entry);
+		ListCell   *rest = lnext(entry);
+		ListCell   *prev = entry;
+
+		/*
+		 * 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.
+		 */
+		if (coldef->typeName == NULL)
+			ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column \"%s\" does not exist",
+						coldef->colname)));
+		while (rest != NULL)
+		{
+			ColumnDef  *restdef = lfirst(rest);
+			ListCell   *next = lnext(rest);		/* need to save it in case we
+												 * delete it */
+
+			if (strcmp(coldef->colname, restdef->colname) == 0)
+			{
+				/*
+				 * merge the column options into the column from the
+				 * parent
+				 */
+				coldef->is_not_null = restdef->is_not_null;
+				coldef->raw_default = restdef->raw_default;
+				coldef->cooked_default = restdef->cooked_default;
+				coldef->constraints = restdef->constraints;
+				list_delete_cell(schema, rest, prev);
+			}
+			prev = rest;
+			rest = next;
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -2455,7 +2617,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -3129,6 +3291,11 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
 				break;
 
+			case AT_AttachPartition:
+			case AT_DetachPartition:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
 			default:			/* oops */
 				elog(ERROR, "unrecognized alter table type: %d",
 					 (int) cmd->subtype);
@@ -3240,12 +3407,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3615,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+		case AT_AttachPartition:
+		case AT_DetachPartition:
+			ATSimplePermissions(rel, ATT_TABLE);
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -3516,7 +3691,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, the
+		 * following check is unnecessary, because we did not modify anything
+		 * about it that will change its toasting requirement.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3765,6 +3947,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
+		case AT_AttachPartition:
+			ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+			break;
+		case AT_DetachPartition:
+			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -3950,7 +4138,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4030,6 +4218,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4094,6 +4283,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4481,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4683,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4802,6 +5006,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5248,6 +5457,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5323,6 +5545,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
@@ -5355,6 +5594,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6167,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7916,6 +8178,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10217,6 +10489,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10506,7 @@ ATPrepAddInherit(Relation child_rel)
 static ObjectAddress
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
-	Relation	parent_rel,
-				catalogRelation;
-	SysScanDesc scan;
-	ScanKeyData key;
-	HeapTuple	inheritsTuple;
-	int32		inhseqno;
+	Relation	parent_rel;
 	List	   *children;
 	ObjectAddress address;
 
@@ -10279,37 +10551,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from partitioned table \"%s\"",
 						 parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10590,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 						RelationGetRelationName(child_rel),
 						RelationGetRelationName(parent_rel))));
 
+	/* OK to create inheritance */
+	CreateInheritance(child_rel, parent_rel);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	return address;
+}
+
+/*
+ * CreateInheritance
+ *		Catalog manipulation portion of creating inheritance between a child
+ *		table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	inheritsTuple;
+	int32		inhseqno;
+
+	/* Note: get RowExclusiveLock because we will write pg_inherits below. */
+	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicates in the list of parents, and determine the highest
+	 * inhseqno already present; we'll use the next one for the new parent.
+	 * Also, if proposed child is a partition, it cannot already be inheriting.
+	 *
+	 * Note: we do not reject the case where the child already inherits from
+	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+	 */
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+
+	/* inhseqno sequences start at 1 */
+	inhseqno = 0;
+	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+	{
+		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+		if (inh->inhparent == RelationGetRelid(parent_rel))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+			 errmsg("relation \"%s\" would be inherited from more than once",
+					RelationGetRelationName(parent_rel))));
+
+		if (inh->inhseqno > inhseqno)
+			inhseqno = inh->inhseqno;
+	}
+	systable_endscan(scan);
+
 	/* Match up the columns and bump attinhcount as needed */
 	MergeAttributesIntoExisting(child_rel, parent_rel);
 
@@ -10358,16 +10667,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
-	ObjectAddressSet(address, RelationRelationId,
-					 RelationGetRelid(parent_rel));
-
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
-
-	/* keep our lock on the parent relation until commit */
-	heap_close(parent_rel, NoLock);
-
-	return address;
 }
 
 /*
@@ -10418,7 +10719,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
  * Check columns in child table match up with columns in parent, and increment
  * their attinhcount.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all parent columns must be found in child. Missing columns are an
  * error.  One day we might consider creating new columns like CREATE TABLE
@@ -10436,12 +10737,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10794,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (is_attach_partition)
+				childatt->attislocal = false;
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10511,7 +10824,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
  *
  * Constraints that are marked ONLY in the parent are ignored.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all constraints in parent must be present in the child. One day we
  * may consider adding new constraints like CREATE TABLE does.
@@ -10530,10 +10843,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10610,6 +10927,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (is_attach_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10634,6 +10963,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10647,13 +11016,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  *
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
  */
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
 {
-	Relation	parent_rel;
-	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10662,19 +11029,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
-
-	/*
-	 * 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);
+	bool		is_detach_partition = false;
 
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11042,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_inherits_inhrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
 							  true, NULL, 1, key);
 
@@ -10705,11 +11063,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
@@ -10718,7 +11085,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_attribute_attrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
 							  true, NULL, 1, key);
 	while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10780,7 +11147,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
 							  true, NULL, 1, key);
 
@@ -10811,7 +11178,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 			if (copy_con->coninhcount <= 0)		/* shouldn't happen */
 				elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
-					 RelationGetRelid(rel), NameStr(copy_con->conname));
+					 RelationGetRelid(child_rel), NameStr(copy_con->conname));
 
 			copy_con->coninhcount--;
 			if (copy_con->coninhcount == 0)
@@ -10823,30 +11190,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
-	parent_oid = RelationGetRelid(parent_rel);
-
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
-	drop_parent_dependency(RelationGetRelid(rel),
+	drop_parent_dependency(RelationGetRelid(child_rel),
 						   RelationRelationId,
 						   RelationGetRelid(parent_rel));
-
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take
 	 * multiple object identifiers, we relay oid of parent relation using
 	 * auxiliary_id argument.
 	 */
 	InvokeObjectPostAlterHookArg(InheritsRelationId,
-								 RelationGetRelid(rel), 0,
+								 RelationGetRelid(child_rel), 0,
 								 RelationGetRelid(parent_rel), false);
-
-	/* keep our lock on the parent relation until commit */
-	heap_close(parent_rel, NoLock);
-
-	ObjectAddressSet(address, RelationRelationId, parent_oid);
-
-	return address;
 }
 
 /*
@@ -12531,3 +12888,285 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRel;
+	List	   *childrels;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * attachRel should not already be part of inheritance; either as a child
+	 * table...
+	 */
+	inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or be a RELKIND_RELATION parent table */
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true, NULL,
+							  1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach regular inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(inheritsRel, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* If attachRel is temp, it must belong to this session */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach a temporary relation of another session as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * 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)
+	{
+		List	   *leaf_parts;
+		List	   *parent_quals;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			leaf_parts = find_all_inheritors(RelationGetRelid(attachRel),
+											 AccessExclusiveLock, NULL);
+		else
+			leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		parent_quals = RelationGetPartitionQual(rel, true);
+		foreach(lc, leaf_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			leaf_relid = lfirst_oid(lc);
+			Relation	leaf_rel;
+			List	   *my_quals;
+
+			/* Lock already taken */
+			if (leaf_relid != RelationGetRelid(attachRel))
+				leaf_rel = heap_open(leaf_relid, NoLock);
+			else
+				leaf_rel = attachRel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations need to be scanned.
+			 */
+			if (leaf_rel != attachRel &&
+				leaf_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(leaf_rel, NoLock);
+				continue;
+			}
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+			/*
+			 * We've got to check only the bound condition specified in this
+			 * command and the parent's bound condition if it happens to be a
+			 * partition.
+			 */
+			my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+			tab->partition_quals = list_concat(parent_quals, my_quals);
+
+			/* keep our lock until commit */
+			if (leaf_rel != attachRel)
+				heap_close(leaf_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that this partition is now included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..c0d48d0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4215,6 +4216,46 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_SCALAR_FIELD(lowerinc);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_SCALAR_FIELD(upperinc);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+	COPY_SCALAR_FIELD(skip_validate);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5138,6 +5179,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d0391d..c2cb140 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,40 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_SCALAR_FIELD(lowerinc);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_SCALAR_FIELD(upperinc);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+	COMPARE_SCALAR_FIELD(skip_validate);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3430,6 +3465,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..d5eb0cc 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,28 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_BOOL_FIELD(lowerinc);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_BOOL_FIELD(upperinc);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3916,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..655aa1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,38 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_BOOL_FIELD(lowerinc);
+	READ_NODE_FIELD(lowerdatums);
+	READ_BOOL_FIELD(upperinc);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2529,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 76c0793..c9f1d79 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -551,6 +552,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <boolean>		lb_inc ub_inc
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
+%type <boolean>		opt_validate_spec
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -576,7 +586,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 /* ordinary key words in alphabetical order */
 %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
-	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
 	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
 	BOOLEAN_P BOTH BY
@@ -592,7 +602,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
@@ -606,7 +617,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
 
@@ -2378,6 +2389,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:
@@ -2473,6 +2516,88 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START '(' range_datum_list ')' lb_inc
+						 END_P '(' range_datum_list ')' ub_inc
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerinc = $7;
+					n->lowerdatums = $5;
+					n->upperinc = $12;
+					n->upperdatums = $10;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+lb_inc:
+			EXCLUSIVE		{ $$ = false;}
+			| INCLUSIVE		{ $$ = true; }
+			| /* EMPTY */	{ $$ = true; }
+		;
+
+ub_inc:
+			INCLUSIVE		{ $$ = true; }
+			| EXCLUSIVE		{ $$ = false; }
+			| /* EMPTY */	{ $$ = false; }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2890,6 +3015,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2935,6 +3098,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2957,6 +3125,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2968,6 +3147,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4739,48 @@ CreateForeignTableStmt:
 					n->options = $14;
 					$$ = (Node *) n;
 				}
+		| CREATE FOREIGN TABLE qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$4->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $4;
+					n->base.inhRelations = list_make1($7);
+					n->base.tableElts = $8;
+					n->base.partbound = (Node *) $9;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = false;
+					/* FDW-specific data */
+					n->servername = $11;
+					n->options = $12;
+					$$ = (Node *) n;
+				}
+		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$7->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $7;
+					n->base.inhRelations = list_make1($10);
+					n->base.tableElts = $11;
+					n->base.partbound = (Node *) $12;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = true;
+					/* FDW-specific data */
+					n->servername = $14;
+					n->options = $15;
+					$$ = (Node *) n;
+				}
 		;
 
 /*****************************************************************************
@@ -13804,6 +14030,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13850,6 +14077,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13892,6 +14120,7 @@ unreserved_keyword:
 			| IMPLICIT_P
 			| IMPORT_P
 			| INCLUDING
+			| INCLUSIVE
 			| INCREMENT
 			| INDEX
 			| INDEXES
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..6a4409a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3026,3 +3045,234 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partitioning key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification as per the partitioning key
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("START must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("END must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+								    *rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index eaf809a..d3979b4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is local to partition.c.
+		 */
+		if (pdesc1->boundinfo != NULL)
+		{
+			if (pdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key,
+										pdesc1->boundinfo, pdesc2->boundinfo))
+				return false;
+		}
+		else if (pdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2608,13 @@ RelationClearRelation(Relation relation, bool rebuild)
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
 
+		/* preserve old partdesc if no logical change */
+		if (keep_partdesc)
+		{
+			SWAPFIELD(PartitionDesc, rd_partdesc);
+			SWAPFIELD(MemoryContext, rd_pdcxt);
+		}
+
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..e1c01d2
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a04dbeb..d648774 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/* Range partition lower and upper bound spec*/
+	bool		lowerinc;
+	List	   *lowerdatums;
+	bool		upperinc;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1570,7 +1615,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
@@ -1796,7 +1843,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..7b290c0 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_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e751491..c3d8c86 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partitioning 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 */
@@ -601,6 +604,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c39fac5..f648fce 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,240 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+-- ATTACH PARTITION
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES START (1) ...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach regular inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+ERROR:  partition constraint is violated by some row
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check that 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
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_3" is already a child of "list_parted".
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted" is already a child of "list_parted".
+-- DETACH PARTITION
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR:  cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partitioning key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partitioning key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index bec5655..fbdfaea 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,193 @@ Partitioning key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partitioning key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES START (1) ...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR:  START must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR:  END must specify exactly one value per partitioning column
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES START (1) END (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES START (1) END (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partitioning key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 16 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..58c9d98 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,201 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+-- ATTACH PARTITION
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 f100498..1d03720 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES START (1) END (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES START (1) END (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1

0004-psql-and-pg_dump-support-for-partitions-11.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-11.patchDownload
From b8662af27ec907b2929cf6a2d935b8ead977a1c9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   86 ++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 419 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 319531c..5662bad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,92 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " START");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						if (!spec->lowerinc)
+							appendStringInfoString(buf, " EXCLUSIVE");
+
+						appendStringInfoString(buf, " END");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						if (spec->upperinc)
+							appendStringInfoString(buf, " INCLUSIVE");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index c12c490..b1c4457 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c393264..834e50d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5624,6 +5631,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14155,6 +14226,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14183,8 +14265,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14213,7 +14298,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14271,15 +14357,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14449,6 +14542,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a75e79c..fc0a345 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6d44378..ca8ff5f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partitioning key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fbdfaea..829d05e 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,46 @@ ERROR:  column "c" named in partitioning key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partitioning key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partitioning key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1d03720..4c0deb6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-11.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-11.patchDownload
From d5425a707788da02a3c9984c30984542da49d515 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   75 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  255 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 ++++++++++
 src/test/regress/expected/update.out   |   27 ++++
 src/test/regress/sql/inherit.sql       |   47 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 581 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..ea3f59a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row for relation \"%s\" violates partition constraint",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..4a3b136 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,258 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..ebc0ba5 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..9813243 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..72c19e8 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,50 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..4bf042e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..4997877 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure-11.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure-11.patchDownload
From cde4510555d55795358f9089e803226ca0d61d34 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  208 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 213 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3ae8cf6..69c3e52 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -887,6 +945,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1325,6 +1430,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index e1c01d2..ab6d3a8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -51,4 +52,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

#119Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#118)
Re: Declarative partitioning - another take

In this latest patch set:

src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
+ *rdatum;

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

On Wed, Nov 9, 2016 at 6:14 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Actually the sentence there is: The parenthesized list of columns or
expressions forms the the <firstterm>partitioning key</firstterm> for the
table

OK.

+            /*
+             * Strip any top-level COLLATE clause.  This ensures that we treat
+             * "x COLLATE y" and "(x COLLATE y)" alike.
+             */

But you don't, right? Unless I am confused, which is possible, the
latter COLLATE will be ignored, while the former one will set the
collation to be used in the context of partitioning comparisons.

The code immediately following the comment does in fact strip the
top-level COLLATE clause.

while (IsA(expr, CollateExpr))
expr = (Node *) ((CollateExpr *) expr)->arg;

So that the following two specifications are equivalent which is the intent:

create table p (a text) partition by range (a collate "en_US");
vs.
create table p (a text) partition by range ((a collate "en_US"));

I see. You're right.

Re-reviewing 0002:

+       if (fout->remoteVersion >= 100000)
+       {
+               PQExpBuffer acl_subquery = createPQExpBuffer();
+               PQExpBuffer racl_subquery = createPQExpBuffer();
+               PQExpBuffer initacl_subquery = createPQExpBuffer();
+               PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+               PQExpBuffer attacl_subquery = createPQExpBuffer();
+               PQExpBuffer attracl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+               PQExpBuffer attinitracl_subquery = createPQExpBuffer();

It seems unnecessary to repeat all of this. The only differences
between the 10000 version and the 9600 version are:

60,61c60
< "AS changed_acl, "
< "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
---

"AS changed_acl "

73c72
< "WHERE c.relkind in ('%c', '%c', '%c', '%c',
'%c', '%c', '%c') "
---

"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "

87,88c86
< RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
< RELKIND_PARTITIONED_TABLE);
---

RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);

But none of that is really a problem. Sure, the 'P' case will never
arise in 9.6, but so what? I'd really like to not keep duplicating
these increasingly-complex hunks of code if we can find some way to
avoid that.

We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
getTables() sends to pre-10 servers, right? But I suppose we need not
call it in that particular SQL query in the first place.

Oh, yeah, that's a problem; the query will error out against older
servers. You could do something like:

char *partkeydef;
if (version <= 90600)
partkeydef = "NULL";
else
partkeydef = "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END";

...and the use %s to interpolate that into the query string.

How about we do it in the following manner in getSchemaData():

if (g_verbose)
write_msg(NULL, "reading partition key information for interesting
tables\n");
getTablePartitionKeyInfo(fout, tblinfo, numTables);

I have implemented the same.

That might be OK too; I'll have to read it through carefully.

Re-reviewing 0003:

+     <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.
+     </para>

This would, presumably, also be true for inheritance. I think we
could just leave this out.

Actually, it isn't true for the regular inheritance situation.

create table parent (a int not null);
create table child () inherits (parent);
alter table child alter a drop not null; -- this works (bug?)

Hrm, OK. That doesn't satisfy MY idea of the principle of least
astonishment, but hey...

But in the meantime, we can proceed with enforcing inheritance on NOT NULL
constraint for *partitions*, because they only ever have one parent and
hence do not need elaborate coninhcount based scheme, I think. Thoughts?

I agree.

+ correspond to the partitioning method and partitioning key of the target

I think that in most places were are referring to the "partitioning
method" (with ing) but the "partition key" (without ing). Let's try to
be consistent.

I'm inclined to switch to "partitioning method" and "partitioning key",
but do you mean just the documentation or throughout? Beside
documentation, I mean source code comments, error messages, etc. I have
assumed throughout.

I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".

+      A full table scan is performed on the table being attached to check that
+      existing rows in the table are within the specified partition bound.
+      If it is known in advance that no partition bound violating rows are
+      present in the table, the above scan can be skipped by specifying the
+      <literal>NO VALIDATE</> option.
+      Default behavior is to perform the scan, as if the
<literal>VALIDATE</literal>
+      option were specified.

I don't think it's OK to allow the partition to be added if it
contains rows that might not be valid. We are generally vary wary
about adding options that can cause data integrity failures and I
think we shouldn't start here, either. On the other hand, it's also
not desirable for adding a partition to take O(n) time in all cases.
So what would be nice is if the new partition could self-certify that
contains no problematic rows by having a constraint that matches the
new partitioning constraint. Then we can skip the scan if that
constraint is already present.

I agree that NO VALIDATE is undesirable and it would be better if we could
get rid of the same. I want to clarify one thing though: what does it
mean for the new partition to have a constraint that *matches* the new
partitioning constraint? Does it mean the new partition's constraint
*implies* the partitioning constraint? Or as certified by equal()?

Implies would be better, but equal() might be tolerable.

+                                                       /*
+                                                        * Never put a
null into the values array, flag
+                                                        * instead for
the code further down below where
+                                                        * we
construct the actual relcache struct.
+                                                        */
+
found_null_partition = true;
+
null_partition_index = i;

How about we elog(ERROR, ...) if found_null_partition is already set?

Makes sense. However, let me mention here that duplicates either within
one partition's list or across the partitions are not possible. That's
because in case of the former, we de-duplicate before storing the list
into the catalog and the latter would simply be an overlap error. Could
this be made an Assert() then?

Well, I think that wouldn't be as good, because for example some
misguided person might do a manual update of the catalog. It seems to
me that if the system catalog contents are questionable, it's better
to error out than to have completely undefined behavior. In other
words, we probably want it checked in non-Assert builds. See similar
cases where we have things like:

elog(ERROR, "conpfeqop is not a 1-D Oid array");

+               /*
+                * Is lower_val = upper_val?
+                */
+               if (lower_val && upper_val)

So, that comment does not actually match that code. That if-test is
just checking whether both bounds are finite. What I think you should
be explaining here is that if lower_val and upper_val are both
non-infinite, and if the happen to be equal, we want to emit an
equality test rather than a >= test plus a <= test because $REASON.

OK, I have added some explanatory comments around this code.

So, I was kind of assuming that the answer to "why are we doing this"
was "for efficiency". It's true that val >= const AND const <= val
can be simplified to val = const, but it wouldn't be necessary to do
so for correctness. It just looks nicer and runs faster.

By the way, if you want to keep the inclusive stuff, I think you
probably need to consider this logic in light of the possibility that
one bound might be exclusive. Maybe you're thinking that can't happen
anyway because it would have been rejected earlier, but an elog(ERROR,
...) might still be appropriate just to guard against messed-up
catalogs.

I forgot this instance of forced-recursion-if-partitioned, fixed. I was
not quite sure what the error message would say; how about:

ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("must truncate child tables too")));

Works fror me.

What happens if you try to attach a table as a partition of itself or
one of its ancestors?

The latter fails with "already a partition" error.

The former case was not being handled at all which has now been fixed.
ATExecAddInherit() prevents that case as an instance of preventing the
circularity of inheritance. It says: ERROR: circular inheritance not
allowed. And then: DETAIL: "rel" is already a child of "rel".

Should ATExecAttachPartition() use the same trick and keep using the same
message(s)? The above detail message doesn't quite sound appropriate when
one tries to attach a table as partition of itself.

Whichever way is easier to code seems fine. It's a pretty obscure
case, so I don't think we need to add code just to get a very slightly
better error message.

+                       | FOR VALUES START '(' range_datum_list ')' lb_inc
+                                                END_P '('
range_datum_list ')' ub_inc

Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
idiomatic than START and END?

It would actually. Should I go ahead and change to FROM (...) TO (...)?

+1!

Related to range partitioning, should we finalize on inclusive START/FROM
and exclusive END/TO preventing explicit specification of the inclusivity?

I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.

Attached updated patches take care of the above comments and few other
fixes. There are still a few items I have not addressed right away:

- Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
new partition's constraints to skip the validation scan
- Remove the syntax to specify inclusivity of each of START and END bounds
of range partitions and instead assume inclusive START and exclusive END

OK - what is the time frame for these changes?

Also, what used to be "[PATCH 5/9] Refactor optimizer's inheritance set
expansion code" is no longer included in the set. I think we can go back
to that topic later as I mentioned last week [2].

Great.

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

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

#120Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#119)
Re: Declarative partitioning - another take

On Wed, Nov 9, 2016 at 12:00 PM, Robert Haas <robertmhaas@gmail.com> wrote:

In this latest patch set:

src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
+ *rdatum;

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

And the pg_upgrade test also fails:

Done
+ pg_dumpall -f /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
+ pg_ctl -m fast stop
waiting for server to shut down.... done
server stopped
+ set +x

Files /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql and
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql differ
dumps were not identical
make[2]: *** [check] Error 1
make[1]: *** [check-pg_upgrade-recurse] Error 2
make: *** [check-world-src/bin-recurse] Error 2
[rhaas pgsql]$ diff
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
6403d6402
< c text
8736,8737c8735
< CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double
precision)),
< CONSTRAINT bmerged CHECK (((b)::double precision > (1)::double precision))
---

CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double precision))

For future revisions, please make sure "make check-world" passes before posting.

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

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

#121Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#119)
Re: Declarative partitioning - another take

On 2016/11/10 2:00, Robert Haas wrote:

In this latest patch set:

src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
+ *rdatum;

This one I will fix.

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

Hm, I can't seem to reproduce this one. Is it perhaps possible that you
applied the patches on top of some other WIP patches or something?

And the pg_upgrade test also fails:

Done
+ pg_dumpall -f /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
+ pg_ctl -m fast stop
waiting for server to shut down.... done
server stopped
+ set +x

Files /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql and
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql differ
dumps were not identical
make[2]: *** [check] Error 1
make[1]: *** [check-pg_upgrade-recurse] Error 2
make: *** [check-world-src/bin-recurse] Error 2
[rhaas pgsql]$ diff
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
6403d6402
< c text
8736,8737c8735
< CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double
precision)),
< CONSTRAINT bmerged CHECK (((b)::double precision > (1)::double

precision))

---

CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double

precision))

This one too I can't seem to reproduce.

For future revisions, please make sure "make check-world" passes before

posting.

OK, I will make sure. FWIW, make check-world passes here after applying
the patches posted yesterday.

Thanks,
Amit

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

#122Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#119)
8 attachment(s)
Re: Declarative partitioning - another take

Thanks again for the prompt review!

On 2016/11/10 2:00, Robert Haas wrote:

On Wed, Nov 9, 2016 at 6:14 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

But none of that is really a problem. Sure, the 'P' case will never
arise in 9.6, but so what? I'd really like to not keep duplicating
these increasingly-complex hunks of code if we can find some way to
avoid that.

We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
getTables() sends to pre-10 servers, right? But I suppose we need not
call it in that particular SQL query in the first place.

Oh, yeah, that's a problem; the query will error out against older
servers. You could do something like:

char *partkeydef;
if (version <= 90600)
partkeydef = "NULL";
else
partkeydef = "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END";

...and the use %s to interpolate that into the query string.

Yeah, that's a way.

I think that in most places were are referring to the "partitioning
method" (with ing) but the "partition key" (without ing). Let's try to
be consistent.

I'm inclined to switch to "partitioning method" and "partitioning key",
but do you mean just the documentation or throughout? Beside
documentation, I mean source code comments, error messages, etc. I have
assumed throughout.

I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".

OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.

I don't think it's OK to allow the partition to be added if it
contains rows that might not be valid. We are generally vary wary
about adding options that can cause data integrity failures and I
think we shouldn't start here, either. On the other hand, it's also
not desirable for adding a partition to take O(n) time in all cases.
So what would be nice is if the new partition could self-certify that
contains no problematic rows by having a constraint that matches the
new partitioning constraint. Then we can skip the scan if that
constraint is already present.

I agree that NO VALIDATE is undesirable and it would be better if we could
get rid of the same. I want to clarify one thing though: what does it
mean for the new partition to have a constraint that *matches* the new
partitioning constraint? Does it mean the new partition's constraint
*implies* the partitioning constraint? Or as certified by equal()?

Implies would be better, but equal() might be tolerable.

I think a equal() -based method would fail to help in most cases. Consider
the following example:

create table foo1 (a int);
alter table foo add constraint check_a check (a < 10 and a >= 1);

create table foo (a int) partition by range (a);
alter table foo attach partition foo1 for values from (1) to (10);

The last command will internally generate the partition constraints that
is basically a list of implicitly AND'd expressions viz. a is not null, a

= 1 and a < 10 which we wrap into a BoolExpr. It would not be equal()

with foo1's constraint even though they are basically the same constraint.
So, simple structural equality may prove to be less productive, IMHO.

It seems a *implies* -based solution would work much better, although a
bit slower for obvious reasons. I reckon slownsess is not a big issue in
this case. So I prototyped the same using predicate_implied_by() which
seems to work reasonably. Looks something like this:

skip_validate = false;
if (predicate_implied_by(partConstraint, existConstraint))
skip_validate = true;

Where partConstraint is 1-member list with the new partition constraint
and existConstraint is a list of the existing constraints of the table
being attached, derived from its TupleConstr.

+                                                       /*
+                                                        * Never put a
null into the values array, flag
+                                                        * instead for
the code further down below where
+                                                        * we
construct the actual relcache struct.
+                                                        */
+
found_null_partition = true;
+
null_partition_index = i;

How about we elog(ERROR, ...) if found_null_partition is already set?

Makes sense. However, let me mention here that duplicates either within
one partition's list or across the partitions are not possible. That's
because in case of the former, we de-duplicate before storing the list
into the catalog and the latter would simply be an overlap error. Could
this be made an Assert() then?

Well, I think that wouldn't be as good, because for example some
misguided person might do a manual update of the catalog. It seems to
me that if the system catalog contents are questionable, it's better
to error out than to have completely undefined behavior. In other
words, we probably want it checked in non-Assert builds. See similar
cases where we have things like:

elog(ERROR, "conpfeqop is not a 1-D Oid array");

Good point, agreed.

+               /*
+                * Is lower_val = upper_val?
+                */
+               if (lower_val && upper_val)

So, that comment does not actually match that code. That if-test is
just checking whether both bounds are finite. What I think you should
be explaining here is that if lower_val and upper_val are both
non-infinite, and if the happen to be equal, we want to emit an
equality test rather than a >= test plus a <= test because $REASON.

OK, I have added some explanatory comments around this code.

So, I was kind of assuming that the answer to "why are we doing this"
was "for efficiency". It's true that val >= const AND const <= val
can be simplified to val = const, but it wouldn't be necessary to do
so for correctness. It just looks nicer and runs faster.

I guess I ended up with this code following along a bit different way of
thinking about it, but in the end what you're saying is true.

By the way, if you want to keep the inclusive stuff, I think you
probably need to consider this logic in light of the possibility that
one bound might be exclusive. Maybe you're thinking that can't happen
anyway because it would have been rejected earlier, but an elog(ERROR,
...) might still be appropriate just to guard against messed-up
catalogs.

Yes, that makes sense. I guess you mean a case like the one shown below:

create table foo5 partition of foo for values start (3, 10) end (3, 10);
ERROR: cannot create range partition with empty range

I think the following would suffice as a guard (checked only if it turns
out that lower_val and upper_val are indeed equal):

if (i == key->partnatts - 1 && spec->lowerinc != spec->upperinc)
elog(ERROR, "invalid range bound specification");

What happens if you try to attach a table as a partition of itself or
one of its ancestors?

The latter fails with "already a partition" error.

The former case was not being handled at all which has now been fixed.
ATExecAddInherit() prevents that case as an instance of preventing the
circularity of inheritance. It says: ERROR: circular inheritance not
allowed. And then: DETAIL: "rel" is already a child of "rel".

Should ATExecAttachPartition() use the same trick and keep using the same
message(s)? The above detail message doesn't quite sound appropriate when
one tries to attach a table as partition of itself.

Whichever way is easier to code seems fine. It's a pretty obscure
case, so I don't think we need to add code just to get a very slightly
better error message.

OK, so let's keep it same as with regular inheritance.

+                       | FOR VALUES START '(' range_datum_list ')' lb_inc
+                                                END_P '('
range_datum_list ')' ub_inc

Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
idiomatic than START and END?

It would actually. Should I go ahead and change to FROM (...) TO (...)?

+1!

Done!

Related to range partitioning, should we finalize on inclusive START/FROM
and exclusive END/TO preventing explicit specification of the inclusivity?

I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.

OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.

I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.

Attached updated patches take care of the above comments and few other
fixes. There are still a few items I have not addressed right away:

- Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
new partition's constraints to skip the validation scan
- Remove the syntax to specify inclusivity of each of START and END bounds
of range partitions and instead assume inclusive START and exclusive END

OK - what is the time frame for these changes?

I have implemented both of these in the attached patch. As mentioned
above, the logic to skip the validation scan using the new partition's
constraints is still kind of a prototype solution, but it seems to work so
far. Comments on the same would be very helpful.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-12.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-12.patchDownload
From f93e6709a6c388b023ba7c918b4e3669c3fdf705 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 51 files changed, 1948 insertions(+), 54 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..6e1ede8 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a method
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9b600a5..ff5ea81 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1768,6 +1796,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c867ebb..981d19e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -533,6 +565,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-12.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-12.patchDownload
From 5b6ab1f889d8ee35414dbebcb954ea5ec225f9fe Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c443735 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4960,7 +4964,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a60cf95..527d6ad 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-12.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-12.patchDownload
From 2ce3fe1bbf33d3a5a97fc2bc6bbf8daeb88633dd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

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

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6139ab1..31352a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..3c2cb0e 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,11 @@ 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.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +711,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as a partition of the target table.  The partition bound specification
+      must correspond to the partitioning method and partition key of the
+      target table.  The table to be attached must have all the same columns
+      as the target table and no more; moreover, the column types must also
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints present in the target table.
+      If some <literal>CHECK</literal> constraint of the table being attached
+      is marked <literal>NO INHERIT</literal>, the command will fail; such
+      constraints must be recreated without the <literal>NO INHERIT</literal>
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is
+      possible to avoid this potentially expensive scan by adding a valid
+      <literal>CHECK</literal> constraint to the table beforehand that only
+      allows rows satisfying the desired partition constraint.  In this case,
+      it will be determined using such a constraint that existing rows in the
+      table satisfies the partition constraint, so the table scan to check the
+      same will be skipped.  In case of a range partition, it is advised to
+      add a <literal>NOT NULL</literal> constraint to each partition key
+      column, because <literal>NULL</literal> values are disallowed in the
+      partition key columns when using range partitioning.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +773,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +990,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1048,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition it is scanned to verify that
+    existing rows meet the partition constraint.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1122,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1311,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
     ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
 </programlisting></para>
 
+  <para>
+   Attach a partition to range partitioned table:
+<programlisting>
+ALTER TABLE measurement
+    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco');
+</programlisting></para>
+
+  <para>
+   Detach a partition from partitioned table:
+<programlisting>
+ALTER TABLE cities
+    DETACH PARTITION measurement_y2015m12;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033..5d0dcf5 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
+    SERVER server_07;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 6e1ede8..b79a8ee 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +247,50 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partition key of the parent table, and must not overlap with
+      any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 754a08b..64fc283 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3176,3 +3224,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..97cb9a4
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1674 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; has
+						 * same length as the values array above */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;			/* range bound datums */
+	bool   *infinite;		/* infinite flag for each datum */
+	bool	inclusive;		/* bound is inclusive (vs exclusive) */
+	bool	lower;			/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, and we only store one of them.
+ */
+typedef struct PartitionRangeInfo
+{
+	PartitionRangeBound **bounds;
+	int		nbounds;
+	int	   *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+	char	strategy;	/* list or range bounds? */
+
+	union
+	{
+		struct PartitionListInfo	lists;
+		struct PartitionRangeInfo	ranges;
+	} bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	Datum	value;
+	int		index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+												  PartitionRangeBound *,
+												  void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	PartitionListValue **all_values = NULL;
+	int			all_values_count = 0;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	PartitionRangeBound **distinct_bounds = NULL;
+	int			num_distinct_bounds = 0;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.  XXX - perhaps it would be better if we
+		 * did not need to use the parent relation desc in that case, because
+		 * the following looks kind of ugly.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		ReleaseSysCache(tuple);
+		partoids = lappend_oid(partoids, inhrelid);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null_partition = false;
+				null_partition_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null_partition)
+								elog(ERROR, "found null more than once");
+							found_null_partition = true;
+							null_partition_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				all_values_count = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **)
+								  palloc(all_values_count *
+										sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, all_values_count,
+						  sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp,
+						  (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+					lower = make_one_range_bound(key, i, spec->lowerdatums,
+												 true);
+					/*
+					 * In most cases, upper bound of the  i'th partition is
+					 * equal in *value* to the lower bound of the (i+1)'th
+					 * partition, execpt they have different inclusivity.
+					 * We want to keep only the former in the final list of
+					 * bounds by eliminating the latter.  To be able to equate
+					 * the two, we flip the inclusivity flag of the latter,
+					 * so that it matches the former's flag value in exactly
+					 * the cases where the elimination must occur.
+					 */
+					lower->inclusive = !lower->inclusive;
+
+					upper = make_one_range_bound(key, i, spec->upperdatums,
+												 false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				num_distinct_bounds = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.
+					 */
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						num_distinct_bounds++;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				distinct_bounds = (PartitionRangeBound **)
+											palloc0(num_distinct_bounds *
+												sizeof(PartitionRangeBound *));
+				k = 0;
+				prev = NULL;
+				for (i = 0; i < j; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+						distinct_bounds[k++] = cur;
+
+					/*
+					 * If it's a lower bound, flip the inclusive flag to its
+					 * original value.
+					 */
+					if (cur->lower)
+						cur->inclusive = !cur->inclusive;
+
+					prev = cur;
+				}
+				Assert(k == num_distinct_bounds);
+
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->boundinfo = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		result->boundinfo->strategy = key->strategy;
+
+		/* Allocate and initialize with invalid mapping values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionListInfo	listinfo;
+
+				listinfo.nvalues = all_values_count;
+				listinfo.has_null = found_null_partition;
+				listinfo.values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo.indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values and indexes.  While copying the indexes, adjust
+				 * them so that they match for any two list partitioned tables
+				 * with same number of partitions and same lists per partition.
+				 * One way to achieve this is to assign indexes in the same
+				 * order as the least values of the individual lists.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo.values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If this index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo.indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * Assign a valid mapping to NULL-accepting partition, if not
+				 * already done (could happen if such partition accepts only
+				 * NULL and hence not covered in the above loop)
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo.null_index = mapping[null_partition_index];
+				else
+					listinfo.null_index = -1;
+
+				result->boundinfo->bounds.lists = listinfo;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeInfo	rangeinfo;
+
+				rangeinfo.bounds = (PartitionRangeBound **)
+										palloc(num_distinct_bounds *
+											sizeof(PartitionRangeBound *));
+				rangeinfo.indexes = (int *)
+										palloc((num_distinct_bounds + 1) *
+													sizeof(int));
+
+				for (i = 0; i < num_distinct_bounds; i++)
+				{
+					rangeinfo.bounds[i] =
+								copy_range_bound(key, distinct_bounds[i]);
+
+					/* There is no mapping for invalid indexes (ie, -1) */
+					if (rangeinfo.bounds[i]->lower)
+						rangeinfo.indexes[i] = -1;
+					else
+					{
+						int		orig_index = rangeinfo.bounds[i]->index;
+
+						/* If this index has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						rangeinfo.indexes[i] = mapping[orig_index];
+					}
+				}
+				rangeinfo.indexes[i] = -1;
+				rangeinfo.nbounds = num_distinct_bounds;
+
+				result->boundinfo->bounds.ranges = rangeinfo;
+				break;
+			}
+		}
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds (ie, sorted).
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2)
+{
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key,
+								 &b1->bounds.lists, &b2->bounds.lists))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key,
+								  &b1->bounds.ranges, &b2->bounds.ranges))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	Relation		parent;
+	PartitionKey	key;
+	PartitionDesc	pdesc;
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	parent = heap_open(parentId, NoLock); /* already locked */
+	key = RelationGetPartitionKey(parent);
+	pdesc = RelationGetPartitionDesc(parent);
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (pdesc->nparts > 0)
+			{
+				ListCell   *cell;
+				bool		new_has_null = false;
+				PartitionListInfo	listinfo;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+				listinfo = pdesc->boundinfo->bounds.lists;
+				Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+				/* If the new partition's list contains null, set a flag. */
+				foreach (cell, spec->listdatums)
+				{
+					Const *val = lfirst(cell);
+
+					if (val->constisnull)
+					{
+						new_has_null = true;
+						break;
+					}
+				}
+
+				/*
+				 * Now check against listinfo whether the new partition's
+				 * values are not already taken by existing partitions.
+				 */
+				if (new_has_null && listinfo.has_null)
+				{
+					with = listinfo.null_index;
+					overlap = true;
+				}
+				else
+				{
+					foreach (cell, spec->listdatums)
+					{
+						Const  *val = lfirst(cell);
+						int		found = -1;
+
+						/* bsearch a new list's value in listinfo->values */
+						if (!val->constisnull)
+							found = partition_list_values_bsearch(key,
+														listinfo.values,
+														listinfo.nvalues,
+														val->constvalue);
+						if (found >= 0)
+						{
+							overlap = true;
+							with = listinfo.indexes[found];
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+			upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified bounds
+			 */
+			if (partition_rbound_cmp(key, lower, upper) >= 0)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				PartitionRangeInfo rangeinfo;
+				int		  offset1, offset2;
+				bool	  equal = false;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+				rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than the new lower bound.
+				 */
+				offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+											  rangeinfo.nbounds, lower,
+											  partition_rbound_cmp, false, &equal);
+
+				/*
+				 * If equal has been set to true, that means the new bound is
+				 * found to be equal to some existing bound.  offset1 == -1
+				 * means that all existing range bounds are greater than the
+				 * new bound.  If offset >= 0, and there exists a gap between
+				 * the corresponding bound and the next, new partition can
+				 * still manage to fit within the same. To confirm whether it
+				 * is so, check the new upper bound.
+				 */
+				if (!equal &&
+					(offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+				{
+					offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+												  rangeinfo.nbounds, upper,
+												  partition_rbound_cmp, false,
+												  &equal);
+
+					if (equal || offset1 != offset2)
+					{
+						overlap = true;
+						with = rangeinfo.indexes[offset2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					if (offset1 == -1)
+					{
+						Assert(equal);
+						offset1 = 0;
+					}
+					with = rangeinfo.indexes[offset1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(pdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference in there*/
+	Assert(!found_whole_row);
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If the list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, remove the null Const from the list and create a IS NULL
+	 * test and create an OR expression along with ScalarArrayOpExpr. We do
+	 * it this way because the null-valued expression does not have the
+	 * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key, emitting an OpExpr for each using
+	 * the corresponding lower and upper datums as constant operands.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *key_col;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		NullTest	   *nulltest;
+		Oid			operoid;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * Stop at this column if either of lower or upper datum is infinite,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (key_col = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create the test expression */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* This can never be, but it's better to make sure */
+				if (i == key->partnatts - 1)
+					elog(ERROR, "invalid range bound specification");
+
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					key_col = (Node *) makeRelabelType((Expr *) key_col,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) key_col,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val <> upper_val.  Emit expressions
+		 * (key_col >= lower_val) and (key_col < upper_val), then stop.
+		 */
+		if (lower_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTGreaterEqualStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTLessStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * opreator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		/* Make sure that the indexes have been "canonicalized" */
+		Assert(l1->indexes[i] == l2->indexes[i]);
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			value1 = (*(const PartitionListValue **) a)->value,
+					value2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = partition_list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+	bound->inclusive = lower;
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		bound->infinite[i] = datum->infinite;
+
+		if (!bound->infinite[i])
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	result->index = src->index;
+	result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+	result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+	result->inclusive = src->inclusive;
+	result->lower = src->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		result->infinite[i] = src->infinite[i];
+		if (!result->infinite[i])
+			result->datums[i] = datumCopy(src->datums[i],
+										  key->parttypbyval[i],
+										  key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	int		i;
+
+	if (r1->nbounds != r2->nbounds)
+		return false;
+
+	/* Compare each individual range's lower and upper bounds */
+	for (i = 0; i < r1->nbounds; i++)
+	{
+		if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+			return false;
+
+		if (r1->indexes[i] != r2->indexes[i])
+			return false;
+	}
+
+	/* There is one more index than bounds */
+	if (r1->indexes[i] != r2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+	PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int32	cmpval;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (b1->infinite[i] && b2->infinite[i])
+		{
+			/*
+			 * Both are infinity, so they are equal unless one is lower and
+			 * the other not.
+			 */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? -1 : 1;
+		}
+		else if (b1->infinite[i])
+			return b1->lower ? -1 : 1;
+		else if (b2->infinite[i])
+			return b2->lower ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.
+	 */
+	if (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		int32	cmpval = 0;
+
+		/*
+		 * If either of them has infinite element, we can't equate them.
+		 * Even if both were infinite, they'd have opposite signs (it always
+		 * holds that b1 and b2 are lower and upper bound or vice versa).
+		 */
+		if (b1->infinite[i] || b2->infinite[i])
+			return false;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			return false;
+	}
+
+	/*
+	 * All datums are found to be equal, now only if both are inclusive
+	 * or exclusive then they are considered equal.
+	 */
+	if (b1->inclusive == b2->inclusive)
+		return true;
+
+	return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple.  The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal)
+{
+	int		lo, hi, mid;
+
+	lo = -1;
+	hi = n - 1;
+	while (lo < hi)
+	{
+		PartitionRangeBound *cur;
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cur = bounds[mid];
+
+		cmpval = cmp(key, cur, probe);
+		if (is_equal)
+			*is_equal = (cmpval == 0);
+
+		if (cmpval < 0 || (equal_allowed && cmpval == 0))
+			lo = mid;
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ddf443..395e116 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -65,6 +66,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +166,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 */
+	List	   *partition_quals; /* partition check quals for attach partition
+								  * validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +284,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				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);
@@ -346,7 +352,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +452,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +479,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	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
@@ -619,6 +633,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parentRel;
+
+		/* Already have strong enough lock on the parent */
+		parentRel = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partition key of parentRel, so it better have one.
+		 */
+		if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parentRel))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+		heap_close(parentRel, NoLock);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parentId, bound);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partition key information into the catalog.
@@ -1096,6 +1169,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
+		/* Force inheritance recursion, if partitioned table. */
 		if (recurse)
 		{
 			ListCell   *child;
@@ -1117,6 +1191,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1474,7 +1552,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;
@@ -1484,6 +1563,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1508,53 +1588,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * Although we might consider merging such entries in the same way that we
 	 * handle name conflicts for inherited attributes, it seems to make more
 	 * sense to assume such conflicts are errors.
+	 *
+	 * In case of partitions, schema contains only the WITH OPTION entries
+	 * at this point, so the following checks are meaningless yet.
 	 */
-	foreach(entry, schema)
+	if (!is_partition)
 	{
-		ColumnDef  *coldef = lfirst(entry);
-		ListCell   *rest = lnext(entry);
-		ListCell   *prev = entry;
-
-		if (coldef->typeName == NULL)
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
 
 			/*
 			 * 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.
 			 */
-			ereport(ERROR,
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" does not exist",
 							coldef->colname)));
 
-		while (rest != NULL)
-		{
-			ColumnDef  *restdef = lfirst(rest);
-			ListCell   *next = lnext(rest);		/* need to save it in case we
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);	/* need to save it in case we
 												 * delete it */
 
-			if (strcmp(coldef->colname, restdef->colname) == 0)
-			{
-				if (coldef->is_from_type)
+				if (strcmp(coldef->colname, restdef->colname) == 0)
 				{
-					/*
-					 * merge the column options into the column from the type
-					 */
-					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_from_type = false;
-					list_delete_cell(schema, rest, prev);
-				}
-				else
-					ereport(ERROR,
-							(errcode(ERRCODE_DUPLICATE_COLUMN),
+					if (coldef->is_from_type)
+					{
+						/*
+						 * merge the column options into the column from the
+						 * type
+						 */
+						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_from_type = false;
+						list_delete_cell(schema, rest, prev);
+					}
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_DUPLICATE_COLUMN),
 							 errmsg("column \"%s\" specified more than once",
 									coldef->colname)));
+				}
+				prev = rest;
+				rest = next;
 			}
-			prev = rest;
-			rest = next;
 		}
 	}
 
@@ -1582,18 +1668,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * Cannot inherit from partitioned tables unless the child table is a
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1708,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1718,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1855,6 +1964,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		heap_close(relation, NoLock);
 	}
 
+	/* In case of a partition, there are no new column definitions */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
 	/*
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
@@ -1886,6 +2002,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				Oid			defcollid,
 							newcollid;
 
+				Assert(!is_partition);
+
 				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
@@ -1934,7 +2052,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(newdef->storage))));
 
-				/* Mark the column as locally defined */
+				/* Mark the column as locally defined (unless partition) */
 				def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
 				def->is_not_null |= newdef->is_not_null;
@@ -1968,6 +2086,52 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have a column definition list for a partition, we can check
+	 * whether the referenced column actually exists.
+	 */
+	if (is_partition)
+		schema = list_concat(schema, saved_schema);
+
+	foreach(entry, schema)
+	{
+		ColumnDef  *coldef = lfirst(entry);
+		ListCell   *rest = lnext(entry);
+		ListCell   *prev = entry;
+
+		/*
+		 * 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.
+		 */
+		if (coldef->typeName == NULL)
+			ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column \"%s\" does not exist",
+						coldef->colname)));
+		while (rest != NULL)
+		{
+			ColumnDef  *restdef = lfirst(rest);
+			ListCell   *next = lnext(rest);		/* need to save it in case we
+												 * delete it */
+
+			if (strcmp(coldef->colname, restdef->colname) == 0)
+			{
+				/*
+				 * merge the column options into the column from the
+				 * parent
+				 */
+				coldef->is_not_null = restdef->is_not_null;
+				coldef->raw_default = restdef->raw_default;
+				coldef->cooked_default = restdef->cooked_default;
+				coldef->constraints = restdef->constraints;
+				list_delete_cell(schema, rest, prev);
+			}
+			prev = rest;
+			rest = next;
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -2455,7 +2619,7 @@ renameatt(RenameStmt *stmt)
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -3129,6 +3293,11 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
 				break;
 
+			case AT_AttachPartition:
+			case AT_DetachPartition:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
 			default:			/* oops */
 				elog(ERROR, "unrecognized alter table type: %d",
 					 (int) cmd->subtype);
@@ -3240,12 +3409,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3617,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+		case AT_AttachPartition:
+		case AT_DetachPartition:
+			ATSimplePermissions(rel, ATT_TABLE);
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -3516,7 +3693,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, the
+		 * following check is unnecessary, because we did not modify anything
+		 * about it that will change its toasting requirement.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  !tab->partition_quals) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3765,6 +3949,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
+		case AT_AttachPartition:
+			ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+			break;
+		case AT_DetachPartition:
+			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -3950,7 +4140,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->partition_quals)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4030,6 +4220,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4094,6 +4285,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_quals)
+	{
+		needscan = true;
+		partqualstate = (List *)
+							ExecPrepareExpr((Expr *) tab->partition_quals,
+											estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4483,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4685,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4802,6 +5008,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5248,6 +5459,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5323,6 +5547,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
@@ -5355,6 +5596,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6169,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7916,6 +8180,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10217,6 +10491,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10508,7 @@ ATPrepAddInherit(Relation child_rel)
 static ObjectAddress
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
-	Relation	parent_rel,
-				catalogRelation;
-	SysScanDesc scan;
-	ScanKeyData key;
-	HeapTuple	inheritsTuple;
-	int32		inhseqno;
+	Relation	parent_rel;
 	List	   *children;
 	ObjectAddress address;
 
@@ -10279,37 +10553,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from partitioned table \"%s\"",
 						 parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10592,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 						RelationGetRelationName(child_rel),
 						RelationGetRelationName(parent_rel))));
 
+	/* OK to create inheritance */
+	CreateInheritance(child_rel, parent_rel);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	return address;
+}
+
+/*
+ * CreateInheritance
+ *		Catalog manipulation portion of creating inheritance between a child
+ *		table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	inheritsTuple;
+	int32		inhseqno;
+
+	/* Note: get RowExclusiveLock because we will write pg_inherits below. */
+	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicates in the list of parents, and determine the highest
+	 * inhseqno already present; we'll use the next one for the new parent.
+	 * Also, if proposed child is a partition, it cannot already be inheriting.
+	 *
+	 * Note: we do not reject the case where the child already inherits from
+	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+	 */
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+
+	/* inhseqno sequences start at 1 */
+	inhseqno = 0;
+	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+	{
+		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+		if (inh->inhparent == RelationGetRelid(parent_rel))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+			 errmsg("relation \"%s\" would be inherited from more than once",
+					RelationGetRelationName(parent_rel))));
+
+		if (inh->inhseqno > inhseqno)
+			inhseqno = inh->inhseqno;
+	}
+	systable_endscan(scan);
+
 	/* Match up the columns and bump attinhcount as needed */
 	MergeAttributesIntoExisting(child_rel, parent_rel);
 
@@ -10358,16 +10669,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
-	ObjectAddressSet(address, RelationRelationId,
-					 RelationGetRelid(parent_rel));
-
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
-
-	/* keep our lock on the parent relation until commit */
-	heap_close(parent_rel, NoLock);
-
-	return address;
 }
 
 /*
@@ -10418,7 +10721,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
  * Check columns in child table match up with columns in parent, and increment
  * their attinhcount.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all parent columns must be found in child. Missing columns are an
  * error.  One day we might consider creating new columns like CREATE TABLE
@@ -10436,12 +10739,16 @@ 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;
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10796,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (is_attach_partition)
+				childatt->attislocal = false;
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10511,7 +10826,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
  *
  * Constraints that are marked ONLY in the parent are ignored.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all constraints in parent must be present in the child. One day we
  * may consider adding new constraints like CREATE TABLE does.
@@ -10530,10 +10845,14 @@ 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);
 
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_attach_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10610,6 +10929,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (is_attach_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10634,6 +10965,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10647,13 +11018,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  *
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
  */
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
 {
-	Relation	parent_rel;
-	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10662,19 +11031,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
-
-	/*
-	 * 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);
+	bool		is_detach_partition = false;
 
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		is_detach_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11044,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_inherits_inhrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
 							  true, NULL, 1, key);
 
@@ -10705,11 +11065,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
@@ -10718,7 +11087,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_attribute_attrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
 							  true, NULL, 1, key);
 	while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10780,7 +11149,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
 							  true, NULL, 1, key);
 
@@ -10811,7 +11180,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 			if (copy_con->coninhcount <= 0)		/* shouldn't happen */
 				elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
-					 RelationGetRelid(rel), NameStr(copy_con->conname));
+					 RelationGetRelid(child_rel), NameStr(copy_con->conname));
 
 			copy_con->coninhcount--;
 			if (copy_con->coninhcount == 0)
@@ -10823,30 +11192,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;
 }
 
 /*
@@ -10913,7 +11272,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	Oid			relid = RelationGetRelid(rel);
 	Type		typetuple;
 	Oid			typeid;
-	Relation	inheritsRelation,
+	Relation	catalogation,
 				relationRelation;
 	SysScanDesc scan;
 	ScanKeyData key;
@@ -10931,19 +11290,19 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	typeid = HeapTupleGetOid(typetuple);
 
 	/* Fail if the table has any inheritance parents. */
-	inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+	catalogation = heap_open(InheritsRelationId, AccessShareLock);
 	ScanKeyInit(&key,
 				Anum_pg_inherits_inhrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(relid));
-	scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+	scan = systable_beginscan(catalogation, InheritsRelidSeqnoIndexId,
 							  true, NULL, 1, &key);
 	if (HeapTupleIsValid(systable_getnext(scan)))
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("typed tables cannot inherit")));
 	systable_endscan(scan);
-	heap_close(inheritsRelation, AccessShareLock);
+	heap_close(catalogation, AccessShareLock);
 
 	/*
 	 * Check the tuple descriptors for compatibility.  Unlike inheritance, we
@@ -12531,3 +12890,379 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				catalog;
+	List	   *childrels;
+	TupleConstr	*attachRel_constr;
+	List	   *partConstraint,
+			   *existConstraint;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	bool		skip_validate = false;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * attachRel should not already be part of inheritance; either as a child
+	 * table...
+	 */
+	catalog = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or be a RELKIND_RELATION parent table */
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+							  1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach regular inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(catalog, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* If attachRel is temp, it must belong to this session */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach a temporary relation of another session as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * Generate partition constraint from the partition bound specification.
+	 * If the parent itself is a partition, make sure to include its
+	 * constraint as well.
+	 */
+	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+														 cmd->bound),
+								 RelationGetPartitionQual(rel, true));
+	partConstraint = (List *) eval_const_expressions(NULL,
+													 (Node *) partConstraint);
+	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+	partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+	/*
+	 * Check if we can do away with having to scan the table being attached
+	 * to validate the partition constraint by *proving* that the existing
+	 * constraints of the table would anyway *imply* the partition constraint.
+	 * We include check constraints and NOT NULL constraints in the list of
+	 * constraints we'll be using for the proof.
+	 */
+	tupleDesc = RelationGetDescr(attachRel);
+	attachRel_constr = tupleDesc->constr;
+	existConstraint = NIL;
+	if (attachRel_constr > 0)
+	{
+		int			num_check = attachRel_constr->num_check;
+		int			i;
+
+		if (attachRel_constr->has_not_null)
+		{
+			int			natts = attachRel->rd_att->natts;
+
+			for (i = 1; i <= natts; i++)
+			{
+				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+				if (att->attnotnull && !att->attisdropped)
+				{
+					NullTest   *ntest = makeNode(NullTest);
+
+					ntest->arg = (Expr *) makeVar(1,
+												  i,
+												  att->atttypid,
+												  att->atttypmod,
+												  att->attcollation,
+												  0);
+					ntest->nulltesttype = IS_NOT_NULL;
+
+					/*
+					 * argisrow=false is correct even for a composite column,
+					 * because attnotnull does not represent a SQL-spec IS NOT
+					 * NULL test in such a case, just IS DISTINCT FROM NULL.
+					 */
+					ntest->argisrow = false;
+					ntest->location = -1;
+					existConstraint = lappend(existConstraint, ntest);
+				}
+			}
+		}
+
+		for (i = 0; i < num_check; i++)
+		{
+			Node	   *cexpr;
+
+			/*
+			 * If this constraint hasn't been fully validated yet, we must
+			 * ignore it here.
+			 */
+			if (!attachRel_constr->check[i].ccvalid)
+				continue;
+
+			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+			/*
+			 * Run each expression through const-simplification and
+			 * canonicalization.  It is necessary, because we will be
+			 * comparing it to similarly-processed qual clauses, and may fail
+			 * to detect valid matches without this.
+			 */
+			cexpr = eval_const_expressions(NULL, cexpr);
+			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+			existConstraint = list_concat(existConstraint,
+										  make_ands_implicit((Expr *) cexpr));
+		}
+
+		existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+		/* And away we go ... */
+		if (predicate_implied_by(partConstraint, existConstraint))
+			skip_validate = true;
+	}
+
+	if (skip_validate)
+		elog(NOTICE, "skipping scan to validate partition constraint");
+
+	/*
+	 * Set up to have the table to be scanned to validate the partition
+	 * constraint (see partConstraint above).  If it's a partitioned table,
+	 * we instead schdule its leaf partitions to be scanned instead.
+	 */
+	if (!skip_validate)
+	{
+		List	   *all_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+											 AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, all_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constraint;
+
+			/* Lock already taken */
+			if (part_relid != RelationGetRelid(attachRel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = attachRel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel != attachRel &&
+				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, part_rel);
+
+			constraint = linitial(partConstraint);
+			tab->partition_quals = make_ands_implicit((Expr *) constraint);
+
+			/* keep our lock until commit */
+			if (part_rel != attachRel)
+				heap_close(part_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that the new partition is now included
+	 * in rel's partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_NODE_FIELD(lowerdatums);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2387df9..b458e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -576,7 +584,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 /* ordinary key words in alphabetical order */
 %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
-	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
 	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
 	BOOLEAN_P BOTH BY
@@ -592,7 +600,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
-	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+	DOUBLE_P DROP
 
 	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -2378,6 +2387,31 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			| ATTACH PARTITION qualified_name ForValues
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
 		;
 
 alter_column_default:
@@ -2473,6 +2507,73 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerdatums = $5;
+					n->upperdatums = $9;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2890,6 +2991,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2935,6 +3074,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2957,6 +3101,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2968,6 +3123,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4715,48 @@ CreateForeignTableStmt:
 					n->options = $14;
 					$$ = (Node *) n;
 				}
+		| CREATE FOREIGN TABLE qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$4->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $4;
+					n->base.inhRelations = list_make1($7);
+					n->base.tableElts = $8;
+					n->base.partbound = (Node *) $9;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = false;
+					/* FDW-specific data */
+					n->servername = $11;
+					n->options = $12;
+					$$ = (Node *) n;
+				}
+		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$7->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $7;
+					n->base.inhRelations = list_make1($10);
+					n->base.tableElts = $11;
+					n->base.partbound = (Node *) $12;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = true;
+					/* FDW-specific data */
+					n->servername = $14;
+					n->options = $15;
+					$$ = (Node *) n;
+				}
 		;
 
 /*****************************************************************************
@@ -13804,6 +14006,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13850,6 +14053,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4175ef5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3026,3 +3045,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partition key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("FROM must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("TO must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+									*rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2d16ea..eb16f70 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is local to partition.c.
+		 */
+		if (pdesc1->boundinfo != NULL)
+		{
+			if (pdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key,
+										pdesc1->boundinfo, pdesc2->boundinfo))
+				return false;
+		}
+		else if (pdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2608,13 @@ RelationClearRelation(Relation relation, bool rebuild)
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
 
+		/* preserve old partdesc if no logical change */
+		if (keep_partdesc)
+		{
+			SWAPFIELD(PartitionDesc, rd_partdesc);
+			SWAPFIELD(MemoryContext, rd_pdcxt);
+		}
+
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..e1c01d2
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned.  Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff5ea81..4d8e3d2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/*
+	 * Range partition lower and upper bounds; each member of the lists
+	 * is a PartitionRangeDatum (see below).
+	 */
+	List	   *lowerdatums;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1570,7 +1615,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
@@ -1796,7 +1843,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 981d19e..45d5a0d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -601,6 +604,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fb492ad..7201b12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,243 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+-- ATTACH PARTITION
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach regular inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+ERROR:  partition constraint is violated by some row
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE:  skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+ERROR:  "part_3" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_3" is already a child of "list_parted".
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted" is already a child of "list_parted".
+-- DETACH PARTITION
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR:  cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE:  skipping scan to validate partition constraint
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_1
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0f15c98..01124e1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partition key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR:  TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR:  cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR:  partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..3f3855b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,203 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+-- ATTACH PARTITION
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);	-- fail
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+	a int NOT NULL,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);	-- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- 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 f100498..683b852 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1

0004-psql-and-pg_dump-support-for-partitions-12.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-12.patchDownload
From 71280978356994aa2b9113948fcb770d8b8447a7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c443735..9deb035 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5624,6 +5631,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14155,6 +14226,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14183,8 +14265,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14213,7 +14298,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14271,15 +14357,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14449,6 +14542,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 527d6ad..3f3c777 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-12.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-12.patchDownload
From 5da702d6ee1ec976243ca9d64671e242ad8993d9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    2 +-
 src/backend/executor/execMain.c        |   75 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   76 +++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 598 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,7 @@ CopyFrom(CopyState cstate)
 		if (!skip_tuple)
 		{
 			/* Check the constraints of the tuple */
-			if (cstate->rel->rd_att->constr)
+			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
 			if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..ea3f59a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row for relation \"%s\" violates partition constraint",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..5d58d23 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,79 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..5aedeef 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values)
+-- insert into part_AA_BB (b) values (1);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure-12.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure-12.patchDownload
From 222ba49b7944ef69ce8c8d2d0b2330929fec917e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  208 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 213 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 97cb9a4..ae9e945 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -878,6 +936,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition hierarchy that are themselves partitioned.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids_v2
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids_v2(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1318,6 +1423,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index e1c01d2..ab6d3a8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 /* relcache support functions for partition descriptor */
 extern void RelationBuildPartitionDesc(Relation relation);
@@ -51,4 +52,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables-12.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-12.patchDownload
From dacc77a2301da6e40311d8b71ca2b7707402a0c9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  327 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  205 +++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 +++++-
 src/backend/executor/nodeModifyTable.c  |  142 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   77 +++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   59 ++++++-
 src/test/regress/sql/insert.sql         |   28 +++
 18 files changed, 926 insertions(+), 9 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ae9e945..373e9e5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -194,6 +206,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1442,7 +1456,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1526,6 +1540,284 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above is the array index within pdesc->parts[] of
+		 * the parent rel, however, we want to return the leaf partition index
+		 * across the whole partition tree.  Note that some partitions within
+		 * pdesc->parts[] may be partitioned themselves and hence stand for
+		 * the leaf partitions in their partition subtrees.  We would need to
+		 * skip past the indexes of leaf partitions of all such partition
+		 * subtrees if they are to left of the above returned index.  In fact,
+		 * finding the PartitionTreeNode of the rightmost subtree is enough
+		 * since its offset counts the leaf partitions on its left including
+		 * those of partition subtrees to its left.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where we don't have any partition subtree to the
+			 * left of the index.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to perform recursion as the selected partition is partitioned
+	 * itself.  Locate the PartitionTreeNode corresponding to the partition
+	 * passing it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be
+	 * less or equal with the tuple.  That is, the tuple belongs to the
+	 * partition with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+	 * is indeed marked !lower (that is, it's an upper bound).  If it turns
+	 * out that it is a lower bound then the corresponding index will be -1,
+	 * which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1787,6 +2079,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	if (cmpval == 0 && !bound->inclusive)
+		return bound->lower ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
  * Are two (consecutive) range bounds equal without distinguishing lower
  * and upper?
  */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_part_oids;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_rel_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_part_oids);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_rel_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_part_oids)
+			{
+				Relation	leaf_rel;
+
+				leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_rel_rri,
+								  leaf_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_rel_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_rel_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(leaf_rel);
+					fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(leaf_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_rel_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1705,6 +1801,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2275,7 +2389,8 @@ CopyFrom(CopyState cstate)
 
 	Assert(cstate->rel);
 
-	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
 			ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2431,10 +2548,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2486,6 +2639,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2506,7 +2684,16 @@ CopyFrom(CopyState cstate)
 			if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 				ExecConstraints(resultRelInfo, slot, estate);
 
-			if (useHeapMultiInsert)
+			if (resultRelInfo->ri_FdwRoutine)
+			{
+				resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																slot,
+																NULL);
+				/* AFTER ROW INSERT Triggers */
+				ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+			}
+			else if (useHeapMultiInsert)
 			{
 				/* Add this tuple to the tuple buffer */
 				if (nBufferedTuples == 0)
@@ -2536,7 +2723,8 @@ CopyFrom(CopyState cstate)
 				List	   *recheckIndexes = NIL;
 
 				/* OK, store the tuple and create index entries for it */
-				heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+				heap_insert(resultRelInfo->ri_RelationDesc,
+							tuple, mycid, hi_options, bistate);
 
 				if (resultRelInfo->ri_NumIndices > 0)
 					recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2569,7 +2763,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 395e116..685b184 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1294,6 +1294,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ea3f59a..62bf8b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +537,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1597,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1688,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_part_oids;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_rel_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_part_oids);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_rel_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_part_oids)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	leaf_rel;
+
+			leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_rel_rri,
+							  leaf_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_rel_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_rel_rri, false);
+
+			if (leaf_rel_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_rel_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(leaf_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(leaf_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_rel_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2097,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			/*
+			 * We are only interested in foreign tables.  Note that this will
+			 * also eliminate any partitioned tables since foreign tables can
+			 * only ever be leaf partitions.
+			 */
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index ab6d3a8..09727cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -56,4 +58,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c62946f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..ce01008 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5d58d23..619336b 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -222,6 +222,62 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+ERROR:  no partition of relation "list_parted" found for row
+DETAIL:  Failing row contains (null, 1).
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_nulls       |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(7 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -230,9 +286,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 table list_parted cascade;
-NOTICE:  drop cascades to 5 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff_1_10
 drop cascades to table part_ee_ff_10_20
+drop cascades to table part_nulls
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 5aedeef..210fab4 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- fail (no list partition defined which accepts nulls)
+insert into list_parted (b) values (1);
+create table part_nulls partition of list_parted for values in (null);
+-- ok
+insert into list_parted (b) values (1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-12.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-12.patchDownload
From 35bf77270874505e07b541d87e411e5a797f6178 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#123Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#122)
Re: Declarative partitioning - another take

On Thu, Nov 10, 2016 at 7:40 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".

OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.

Oh, I like "partitioning strategy". Can we standardize on that?

Related to range partitioning, should we finalize on inclusive START/FROM
and exclusive END/TO preventing explicit specification of the inclusivity?

I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.

OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.

I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.

Yes, you need to rip out all of the logic that supports it. Having
the logic to support it but not the syntax is bad because then that
code can't be properly tested.

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

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

#124Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#121)
Re: Declarative partitioning - another take

On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

Hm, I can't seem to reproduce this one. Is it perhaps possible that you
applied the patches on top of some other WIP patches or something?

Nope. I just checked and this passes with only 0001 and 0002 applied,
but when I add 0003 and 0004 then it starts failing. It appears that
the problem starts at this point in the foreign_data test:

ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;

After that command, in the expected output, pt1chk2 stops showing up
in the output of \d+ pt1, but continues to appear in the output of \d+
ft2. With your patch, however, it stops showing up for ft2 also. If
that's not also happening for you, it might be due to an uninitialized
variable someplace.

+ /* Force inheritance recursion, if partitioned table. */

Doesn't match code (any more).

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

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

#125Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#124)
8 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/11 6:51, Robert Haas wrote:

On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

Hm, I can't seem to reproduce this one. Is it perhaps possible that you
applied the patches on top of some other WIP patches or something?

Nope. I just checked and this passes with only 0001 and 0002 applied,
but when I add 0003 and 0004 then it starts failing.

Sorry, it definitely wasn't an error on your part.

It appears that
the problem starts at this point in the foreign_data test:

ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;

After that command, in the expected output, pt1chk2 stops showing up
in the output of \d+ pt1, but continues to appear in the output of \d+
ft2. With your patch, however, it stops showing up for ft2 also. If
that's not also happening for you, it might be due to an uninitialized
variable someplace.

Thanks for the context. I think I found the culprit variable in
MergeConstraintsIntoExisting() and fixed it. As you correctly guessed,
the uninitialized variable caused (in your environment) even non-partition
child relations to be treated partitions and hence forced any merged
constraints to be non-local in all cases, not just in case of partitions.
Which meant the command you quoted would even drop the ft2's (a child)
constraint because its conislocal is wrongly false.

+ /* Force inheritance recursion, if partitioned table. */

Doesn't match code (any more).

Fixed.

I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".

OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.

Oh, I like "partitioning strategy". Can we standardize on that?

OK, done.

I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.

OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.

I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.

Yes, you need to rip out all of the logic that supports it. Having
the logic to support it but not the syntax is bad because then that
code can't be properly tested.

Agreed, done.

Attached updated patches.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-13.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-13.patchDownload
From 793b9487bfe22e5bcb299f326f1c18a93a0c4cc8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    2 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 51 files changed, 1948 insertions(+), 54 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..1a95219 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-13.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-13.patchDownload
From 11b2b9278bbdb515d788c62c87143917c03d7937 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c443735 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4960,7 +4964,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a60cf95..527d6ad 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-13.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-13.patchDownload
From 5eb5c7723697398819cfd3669fbfad58603ad84c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

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

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

0004-psql-and-pg_dump-support-for-partitions-13.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-13.patchDownload
From 16322c06ccdf69bc9c63f3c019030196eb604f28 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c443735..9deb035 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5624,6 +5631,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14155,6 +14226,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14183,8 +14265,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14213,7 +14298,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14271,15 +14357,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14449,6 +14542,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 527d6ad..3f3c777 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-13.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-13.patchDownload
From c6e9257003a3a7f0109eedfc13362f70d2d76bbc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   75 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 608 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..ea3f59a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row for relation \"%s\" violates partition constraint",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure-13.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure-13.patchDownload
From c2a8ec719729249c67caf5fa68912a4a2d2c0dcd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  208 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 213 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7810c9d..6301d91 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -873,6 +931,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition tree that are themselves partitioned tables.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1313,6 +1418,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 062de88..057b1e3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables-13.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-13.patchDownload
From c5e1a7c993f82b9faa1773ceed1d01b99115140d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  325 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  203 +++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   47 +++++-
 src/backend/executor/nodeModifyTable.c  |  142 ++++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   77 ++++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   52 +++++
 src/test/regress/sql/insert.sql         |   25 +++
 18 files changed, 914 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6301d91..e3a0848 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -194,6 +206,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1437,7 +1451,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1521,6 +1535,281 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above considers only this parent (of which partRel
+		 * is a partition).  We however want to return the index across the
+		 * the whole partition tree.  If partRel is the leftmost partition
+		 * of parent (index = 0) or if none of the left siblings are
+		 * partitioned tables, we can simply return parent's offset plus
+		 * partRel's index as the result.  Otherwise, we find the node
+		 * corresponding to the rightmost partitioned table among partRel's
+		 * left siblings and use the information therein.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where all of partRel's left siblings (if any) are
+			 * leaf partitions.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to recurse as the selected partition is a partitioned table
+	 * itself.  Locate the corresponding PartitionTreeNode to pass it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be
+	 * less or equal with the tuple.  That is, the tuple belongs to the
+	 * partition with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+	 * is indeed marked !lower (that is, it's an upper bound).  If it turns
+	 * out that it is a lower bound then the corresponding index will be -1,
+	 * which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1767,6 +2056,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/* If datums are equal and this is an upper bound, tuple > bound */
+	if (cmpval == 0 && !bound->lower)
+		return -1;
+
+	return cmpval;
+}
+
+/*
  * Return whether two range bounds are equal simply by comparing datums
  */
 static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..6fb376f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_parts = get_leaf_partition_oids(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_parts);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	part_rel;
+
+				part_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(part_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  part_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_part_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(part_rel);
+					fdw_private = leaf_part_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(part_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(part_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_part_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1705,6 +1801,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2395,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2506,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2534,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2439,10 +2556,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2647,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2523,7 +2701,16 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
-				if (useHeapMultiInsert)
+				if (resultRelInfo->ri_FdwRoutine)
+				{
+					resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																	slot,
+																	NULL);
+					/* AFTER ROW INSERT Triggers */
+					ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+				}
+				else if (useHeapMultiInsert)
 				{
 					/* Add this tuple to the tuple buffer */
 					if (nBufferedTuples == 0)
@@ -2553,7 +2740,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2765,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2784,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8cd3614..b51d59a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ea3f59a..62bf8b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..5c2bf6c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,31 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	saved_resultRelInfo = resultRelInfo;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		TupleConversionMap *map;
+
+		econtext->ecxt_scantuple = slot;
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+		estate->es_result_relation_info = resultRelInfo;
+
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+			tuple = do_convert_tuple(tuple, map);
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +537,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1597,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1688,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_parts = get_leaf_partition_oids(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_parts);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_part_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2097,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			/*
+			 * We are only interested in foreign tables.  Note that this will
+			 * also eliminate any partitioned tables since foreign tables can
+			 * only ever be leaf partitions.
+			 */
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 057b1e3..86cc598 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -50,4 +52,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c62946f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..ce01008 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-13.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-13.patchDownload
From fbdc4f916d5d8138eee71641d4418aa756f3b944 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#126Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#125)
Re: Declarative partitioning - another take

I have not looked at the latest set of patches, but in the version
that I have we create one composite type for every partition. This
means that if there are thousand partitions, there will be thousand
identical entries in pg_type. Since all the partitions share the same
definition (by syntax), it doesn't make sense to add so many identical
entries. Moreover, in set_append_rel_size(), while translating the
expressions from parent to child, we add a ConvertRowtypeExpr instead
of whole-row reference if reltype of the parent and child do not match
(adjust_appendrel_attrs_mutator())
if (appinfo->parent_reltype != appinfo->child_reltype)
{
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);

I guess, we should set reltype of child to that of parent for
declarative partitions.

On Fri, Nov 11, 2016 at 4:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/11 6:51, Robert Haas wrote:

On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

With all patches applied, "make check" fails with a bunch of diffs
that look like this:

Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)

Hm, I can't seem to reproduce this one. Is it perhaps possible that you
applied the patches on top of some other WIP patches or something?

Nope. I just checked and this passes with only 0001 and 0002 applied,
but when I add 0003 and 0004 then it starts failing.

Sorry, it definitely wasn't an error on your part.

It appears that
the problem starts at this point in the foreign_data test:

ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;

After that command, in the expected output, pt1chk2 stops showing up
in the output of \d+ pt1, but continues to appear in the output of \d+
ft2. With your patch, however, it stops showing up for ft2 also. If
that's not also happening for you, it might be due to an uninitialized
variable someplace.

Thanks for the context. I think I found the culprit variable in
MergeConstraintsIntoExisting() and fixed it. As you correctly guessed,
the uninitialized variable caused (in your environment) even non-partition
child relations to be treated partitions and hence forced any merged
constraints to be non-local in all cases, not just in case of partitions.
Which meant the command you quoted would even drop the ft2's (a child)
constraint because its conislocal is wrongly false.

+ /* Force inheritance recursion, if partitioned table. */

Doesn't match code (any more).

Fixed.

I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".

OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.

Oh, I like "partitioning strategy". Can we standardize on that?

OK, done.

I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.

OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.

I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.

Yes, you need to rip out all of the logic that supports it. Having
the logic to support it but not the syntax is bad because then that
code can't be properly tested.

Agreed, done.

Attached updated patches.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#127Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#123)
Re: Declarative partitioning - another take

I forgot to quote your comments in the email I sent on Friday [1]/messages/by-id/8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp, with
new patches that do take care of the following comments.

On 2016/11/11 4:04, Robert Haas wrote:

On Thu, Nov 10, 2016 at 7:40 AM, Amit Langote

OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.

Oh, I like "partitioning strategy". Can we standardize on that?

Done.

OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.

I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.

Yes, you need to rip out all of the logic that supports it. Having
the logic to support it but not the syntax is bad because then that
code can't be properly tested.

Agreed and done. Now the only thing that dictates the inclusivity of
individual range bounds during comparisons (with other range bounds or
partition key of tuples) is whether the bound is a lower bound or not;
inclusive if, exclusive if not.

Thanks,
Amit

[1]: /messages/by-id/8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp
/messages/by-id/8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp

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

#128Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#110)
Re: Declarative partitioning - another take

On 2016/11/04 0:49, Robert Haas wrote:

On Thu, Nov 3, 2016 at 7:46 AM, <alvherre@alvh.no-ip.org> wrote:

El 2016-10-28 07:53, Amit Langote escribió:

@@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab,
Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+       if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("cannot reference relation
\"%s\"", RelationGetRelationName(pkrel)),
+                                errdetail("Referencing partitioned tables
in foreign key constraints is not supported.")));

Is there a plan for fixing this particular limitation? It's a pretty
serious problem for users,
and the suggested workaround (to create a separate non-partitioned table
which carries only the PK
columns which is updated by triggers, and direct the FKs to it instead of to
the partitioned table)
is not only a very ugly one, but also very slow.

If you have two compatibly partitioned tables, and the foreign key
matches the partitioning keys, you could implement a foreign key
between the two tables as a foreign key between each pair of matching
partitions. Otherwise, isn't the only way to handle this a global
index?

I am assuming you don't mean a global index (on partitioned tables) as in
some new kind of monolithic physical structure that implements the
constraint across tables (partitions), right? I'm thinking you mean a
collection of btree indexes on individual partitions with the key of each
index matching the partition key of the parent, created internally as part
of the creation of the same index on the parent. In fact, the said
indexes are created and maintained sort of like how inherited attributes,
constraints are. That would require quite a bit of new infrastructure.
We did discuss about the possibility of such a feature being implemented
on top of declarative partitioning, but not in version 1 [1]/messages/by-id/CA+TgmoZZMfcf16YaHuhP1Vk=j8PDFeHCvfj+FJQd+eFhs+7P8A@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZZMfcf16YaHuhP1Vk=j8PDFeHCvfj+FJQd+eFhs+7P8A@mail.gmail.com
/messages/by-id/CA+TgmoZZMfcf16YaHuhP1Vk=j8PDFeHCvfj+FJQd+eFhs+7P8A@mail.gmail.com

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

#129Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#126)
Re: Declarative partitioning - another take

On 2016/11/11 20:49, Ashutosh Bapat wrote:

I have not looked at the latest set of patches, but in the version
that I have we create one composite type for every partition. This
means that if there are thousand partitions, there will be thousand
identical entries in pg_type. Since all the partitions share the same
definition (by syntax), it doesn't make sense to add so many identical
entries. Moreover, in set_append_rel_size(), while translating the
expressions from parent to child, we add a ConvertRowtypeExpr instead
of whole-row reference if reltype of the parent and child do not match
(adjust_appendrel_attrs_mutator())
if (appinfo->parent_reltype != appinfo->child_reltype)
{
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);

I guess, we should set reltype of child to that of parent for
declarative partitions.

Thanks for the suggestion. I agree that partitions having the same
reltype as the parent will help a number of cases including the one you
mentioned, but I haven't yet considered how invasive such a change will be.

Thanks,
Amit

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

#130Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#125)
8 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/11 19:30, Amit Langote wrote:

Attached updated patches.

Here is the latest version of the patches with some fixes along with those
mentioned below (mostly in 0003):

- Fixed the logic to skip the attach partition validation scan such that
it won't skip scanning a list partition *that doesn't accept NULLs* if
the partition key column is not set NOT NULL (it similarly doesn't skip
scanning a range partition if either of the partition key columns is not
set NOT NULL, because a range partition key cannot contain NULLs at all)

- Added some more regression tests for ATTACH PARTITION command

- Some fixes to documentation and source code comments

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-14.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-14.patchDownload
From 9471d830fa2a34f51a778d06be85937f82e463d7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 52 files changed, 1952 insertions(+), 56 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..1a95219 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-14.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-14.patchDownload
From bbe96ba88f247fb9b288436c2f2227844f22a05c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ee1f673..fb92e7f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1226,9 +1226,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2085,6 +2086,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4959,7 +4963,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4973,7 +4977,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5501,7 +5506,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6910,6 +6917,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14262,6 +14310,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14322,6 +14373,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14340,7 +14392,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14557,6 +14610,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 642c4d5..f33f86d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -311,6 +311,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -649,5 +650,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-14.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-14.patchDownload
From aa739e51bda92fc6a6e1eb319e0a8003e50da548 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

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

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

0004-psql-and-pg_dump-support-for-partitions-14.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-14.patchDownload
From c1c61624561a7ec97d0ef378f4f66ba4ec5fe032 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index fb92e7f..57e626c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5608,9 +5608,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5637,6 +5644,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14168,6 +14239,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14196,8 +14278,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14226,7 +14311,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14284,15 +14370,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14462,6 +14555,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f33f86d..dd5ad8f 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -321,6 +321,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -461,6 +463,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-14.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-14.patchDownload
From 846fad4219386ebe6078353ded425ccfcf8fd528 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   75 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 608 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..ea3f59a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row for relation \"%s\" violates partition constraint",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure-14.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure-14.patchDownload
From f3c16c07eebe39710fdf721098d442f1ecd1397c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  208 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 213 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e21e7ad..a5800fb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -114,6 +114,61 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	index				If a partition ourselves, index in the parent's
+ *						partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				0-based index of the first leaf partition
+ *						in the partition tree rooted at this node
+ *
+ *	downlink			Link to our leftmost child node (ie, corresponding
+ *						to first of our partitions that is itself
+ *						partitioned)
+ *
+ *	next				Link to the right sibling node on a given level
+ *						(ie, corresponding to the next partition on the same
+ *						level that is itself partitioned)
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -124,6 +179,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -874,6 +932,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form partition tree rooted at this rel's node
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition tree that are themselves partitioned tables.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids(PartitionTreeNode ptnode)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, ptnode->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1314,6 +1419,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * 'offset' is 0-based index of the first leaf node in this subtree. During
+ * the first invocation, a 0 will be pass
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, AccessShareLock);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, AccessShareLock);
+
+		/* Found our first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at ptnode
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode ptnode)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = ptnode->downlink;
+
+	for (i = 0; i < ptnode->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 062de88..057b1e3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables-14.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-14.patchDownload
From 4cd487103538ac8cab74aa8aac75ffec4f42f02c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  327 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  203 +++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   49 +++++-
 src/backend/executor/nodeModifyTable.c  |  156 +++++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   77 +++++++
 src/backend/optimizer/util/plancat.c    |   13 ++
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/include/optimizer/plancat.h         |    1 +
 src/test/regress/expected/insert.out    |   52 +++++
 src/test/regress/sql/insert.sql         |   25 +++
 18 files changed, 932 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index a5800fb..e7e0c7c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -182,6 +182,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
 static int get_leaf_partition_count(PartitionTreeNode ptnode);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1438,7 +1452,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1522,6 +1536,283 @@ get_leaf_partition_count(PartitionTreeNode ptnode)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/*
+ * FormPartitionKeyDatum
+ *		Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+		/* Check caller has set up context correctly */
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode ptnode,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	Relation				partRel;
+	PartitionKeyExecInfo   *pkinfo = ptnode->pkinfo;
+	PartitionTreeNode		node;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		index;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (ptnode->pdesc->nparts == 0)
+	{
+		*failed_at = ptnode->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	/* Disallow nulls, if range partition key */
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+		if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRATEGY_RANGE)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key contains null")));
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											 values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+											  values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (index < 0)
+	{
+		*failed_at = ptnode->relid;
+		return index;
+	}
+
+	partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+	/* Don't recurse if the index'th partition is a leaf partition. */
+	if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionTreeNode	prev;
+
+		/*
+		 * Index returned above considers only this parent (of which partRel
+		 * is a partition).  We however want to return the index across the
+		 * the whole partition tree.  If partRel is the leftmost partition
+		 * of parent (index = 0) or if none of the left siblings are
+		 * partitioned tables, we can simply return parent's offset plus
+		 * partRel's index as the result.  Otherwise, we find the node
+		 * corresponding to the rightmost partitioned table among partRel's
+		 * left siblings and use the information therein.
+		 */
+		prev = node = ptnode->downlink;
+		if (node && node->index < index)
+		{
+			/*
+			 * Find the partition tree node such that its index value is the
+			 * greatest value less than the above returned index.
+			 */
+			while (node)
+			{
+				if (node->index > index)
+				{
+					node = prev;
+					break;
+				}
+
+				prev = node;
+				node = node->next;
+			}
+
+			if (!node)
+				node = prev;
+			Assert (node != NULL);
+
+			index = node->offset + node->num_leaf_parts +
+										(index - node->index - 1);
+		}
+		else
+			/*
+			 * The easy case where all of partRel's left siblings (if any) are
+			 * leaf partitions.
+			 */
+			index = ptnode->offset + index;
+
+		heap_close(partRel, NoLock);
+		return index;
+	}
+
+	heap_close(partRel, NoLock);
+
+	/*
+	 * Need to recurse as the selected partition is a partitioned table
+	 * itself.  Locate the corresponding PartitionTreeNode to pass it down.
+	 */
+	node = ptnode->downlink;
+	while (node->next != NULL && node->index != index)
+		node = node->next;
+	Assert (node != NULL);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple (arg 'value' contains the
+ *		list partition key of the original tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Get the index of the range partition for a tuple (arg 'tuple'
+ *		actually contains the range partition key of the original
+ *		tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be less
+	 * or equal with the tuple.  That is, the tuple belongs to the partition
+	 * with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter is
+	 * indeed an upper (!lower) bound.  If it turns out otherwise, the
+	 * corresponding index will be -1, which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1767,6 +2058,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/* If datums are equal and this is an upper bound, tuple > bound */
+	if (cmpval == 0 && !bound->lower)
+		return -1;
+
+	return cmpval;
+}
+
+/*
  * Return whether two range bounds are equal simply by comparing datums
  */
 static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..6fb376f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		ptnode;	/* partition descriptor node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			Query		*parse = makeNode(Query);		/* ditto */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			cstate->ptnode = RelationGetPartitionTreeNode(rel);
+			leaf_parts = get_leaf_partition_oids(cstate->ptnode);
+			num_leaf_parts = list_length(leaf_parts);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			parse->rtable = list_make1(fdw_rte);
+			root->parse = parse;
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	part_rel;
+
+				part_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(part_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  part_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_part_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(part_rel);
+					fdw_private = leaf_part_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(part_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(part_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_part_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+			pfree(fdw_rte);
+			pfree(plan);
+			pfree(parse);
+			pfree(root);
+		}
 	}
 	else
 	{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
 static void
 EndCopy(CopyState cstate)
 {
+	int		i;
+
 	if (cstate->is_program)
 	{
 		ClosePipeToProgram(cstate);
@@ -1705,6 +1801,23 @@ EndCopy(CopyState cstate)
 							cstate->filename)));
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < cstate->num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		/* XXX - EState not handy here to pass to EndForeignModify() */
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+		if (cstate->partition_tupconv_maps[i])
+			pfree(cstate->partition_tupconv_maps[i]);
+	}
+
 	MemoryContextDelete(cstate->copycontext);
 	pfree(cstate);
 }
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2395,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2506,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2534,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->ptnode != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2439,10 +2556,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->ptnode)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2647,31 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition */
+		saved_resultRelInfo = resultRelInfo;
+		if (cstate->ptnode)
+		{
+			int		i_leaf_partition;
+			TupleConversionMap *map;
+
+			econtext->ecxt_scantuple = slot;
+			i_leaf_partition = ExecFindPartition(resultRelInfo,
+												 cstate->ptnode,
+												 slot,
+												 estate);
+			Assert(i_leaf_partition >= 0 &&
+				   i_leaf_partition < cstate->num_partitions);
+
+			resultRelInfo = cstate->partitions + i_leaf_partition;
+			estate->es_result_relation_info = resultRelInfo;
+
+			map = cstate->partition_tupconv_maps[i_leaf_partition];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2523,7 +2701,16 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
-				if (useHeapMultiInsert)
+				if (resultRelInfo->ri_FdwRoutine)
+				{
+					resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																	slot,
+																	NULL);
+					/* AFTER ROW INSERT Triggers */
+					ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+				}
+				else if (useHeapMultiInsert)
 				{
 					/* Add this tuple to the tuple buffer */
 					if (nBufferedTuples == 0)
@@ -2553,7 +2740,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2765,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2784,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3b72ae3..6478211 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ea3f59a..b5125a8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3001,43 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		i_leaf_partition;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate,
+											   &failed_at);
+
+	if (i_leaf_partition < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..44801b8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,45 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	if (mtstate->mt_partition_tree_root)
+	{
+		int		i_leaf_partition;
+		TupleConversionMap *map;
+
+		/*
+		 * Get the index of leaf partitions that we'll use to get the correct
+		 * ResultRelInfo from mtstate->mt_partitions[].
+		 */
+		i_leaf_partition = ExecFindPartition(resultRelInfo,
+											 mtstate->mt_partition_tree_root,
+											 slot,
+											 estate);
+		Assert(i_leaf_partition >= 0 &&
+			   i_leaf_partition < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +551,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1611,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1702,98 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+		leaf_parts = get_leaf_partition_oids(mtstate->mt_partition_tree_root);
+		num_leaf_parts = list_length(leaf_parts);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_part_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2111,23 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+
+		if (node->mt_partition_tupconv_maps[i])
+			pfree(node->mt_partition_tupconv_maps[i]);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			/*
+			 * We are only interested in foreign tables.  Note that this will
+			 * also eliminate any partitioned tables since foreign tables can
+			 * only ever be leaf partitions.
+			 */
+			if (!oid_is_foreign_table(myoid))
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+				pfree(fdw_rte);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		pfree(root->simple_rte_array);
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
 	heap_close(relation, NoLock);
 	return result;
 }
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+	Relation	rel;
+	char		relkind;
+
+	rel = heap_open(relid, NoLock);
+	relkind = rel->rd_rel->relkind;
+	heap_close(rel, NoLock);
+
+	return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 057b1e3..86cc598 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -50,4 +52,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
 extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
+
+extern int get_partition_for_tuple(PartitionTreeNode ptnode,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..c62946f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode ptnode,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..ce01008 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree_root;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
 				 SpecialJoinInfo *sjinfo);
 
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
 
 #endif   /* PLANCAT_H */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-14.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-14.patchDownload
From f252a19667618f395fc9617a7889ff79ad002f61 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#131Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#130)
Re: Declarative partitioning - another take

On Tue, Nov 15, 2016 at 5:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/11 19:30, Amit Langote wrote:

Attached updated patches.

Here is the latest version of the patches with some fixes along with those
mentioned below (mostly in 0003):

- Fixed the logic to skip the attach partition validation scan such that
it won't skip scanning a list partition *that doesn't accept NULLs* if
the partition key column is not set NOT NULL (it similarly doesn't skip
scanning a range partition if either of the partition key columns is not
set NOT NULL, because a range partition key cannot contain NULLs at all)

- Added some more regression tests for ATTACH PARTITION command

- Some fixes to documentation and source code comments

Have you done any performance testing on the tuple routing code?
Suppose we insert a million (or 10 million) tuples into an
unpartitioned table, a table with 10 partitions, a table with 100
partitions, a table with 1000 partitions, and a table that is
partitioned into 10 partitions each of which has 10 subpartitions.
Ideally, the partitioned cases would run almost as fast as the
unpartitioned case, but probably there will be some overhead.
However, it would be useful to know how much. Also, it would be
useful to set up the same cases with inheritance using a PL/pgsql ON
INSERT trigger for tuple routing and compare. Hopefully the tuple
routing code is far faster than a trigger, but we should make sure
that's the case and look for optimizations if not. Also, it would be
useful to know how much slower the tuple-mapping-required case is than
the no-tuple-mapping-required case.

I think the comments in some of the later patches could use some work
yet. For example, in 0007, FormPartitionKeyDatum()'s header comment
is largely uninformative, get_partition_for_tuple()'s header comment
doesn't explain what the return value means in the non-failure case,
and ExecFindPartition() doesn't have a header comment at all.

The number of places in these patches where you end up reopening a
hopefully-already-locked relation with NoLock (or sometimes with
AccessShareLock) is worrying to me. I think that's a coding pattern
we should be seeking to avoid; every one of those is not only a hazard
(what if we reach that point in the code without a lock?) but a
possible performance issue (we have to look up the OID in the
backend-private hash table; and if you are passing AccessShareLock
then you might also hit the lock manager which could be slow or create
deadlock hazards). It would be much better to pass the Relation
around rather than the OID whenever possible.

Also, in 0006:

- I doubt that PartitionTreeNodeData's header comment will survive
contact with pgindent.

In 0007:

- oid_is_foreign_table could/should do a syscache lookup instead of
opening the heap relation. But actually I think you could drop it
altogether and use get_rel_relkind().

- The XXX in EndCopy will need to be fixed somehow.

- I suspect that many of the pfree's in this patch are pointless
because the contexts in which those allocations are performed will be
reset or deleted shortly afterwards anyway. Only pfree data that
might otherwise live substantially longer than we want, because pfree
consumes some cycles.

- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.

- I don't understand how it can be right for get_partition_for_tuple()
to be emitting an error that says "range partition key contains null".
A defective partition key should be detected at partition creation
time, not in the middle of tuple routing.

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

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

#132Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#131)
8 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/16 4:21, Robert Haas wrote:

On Tue, Nov 15, 2016 at 5:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/11 19:30, Amit Langote wrote:

Attached updated patches.

Have you done any performance testing on the tuple routing code?
Suppose we insert a million (or 10 million) tuples into an
unpartitioned table, a table with 10 partitions, a table with 100
partitions, a table with 1000 partitions, and a table that is
partitioned into 10 partitions each of which has 10 subpartitions.
Ideally, the partitioned cases would run almost as fast as the
unpartitioned case, but probably there will be some overhead.
However, it would be useful to know how much. Also, it would be
useful to set up the same cases with inheritance using a PL/pgsql ON
INSERT trigger for tuple routing and compare. Hopefully the tuple
routing code is far faster than a trigger, but we should make sure
that's the case and look for optimizations if not. Also, it would be
useful to know how much slower the tuple-mapping-required case is than
the no-tuple-mapping-required case.

OK, I will share the performance results soon.

Meanwhile, here are updated patch that address most of the following comments.

I think the comments in some of the later patches could use some work
yet. For example, in 0007, FormPartitionKeyDatum()'s header comment
is largely uninformative, get_partition_for_tuple()'s header comment
doesn't explain what the return value means in the non-failure case,
and ExecFindPartition() doesn't have a header comment at all.

Sorry, I have tried to make these comments more informative in the attached.

The number of places in these patches where you end up reopening a
hopefully-already-locked relation with NoLock (or sometimes with
AccessShareLock) is worrying to me. I think that's a coding pattern
we should be seeking to avoid; every one of those is not only a hazard
(what if we reach that point in the code without a lock?) but a
possible performance issue (we have to look up the OID in the
backend-private hash table; and if you are passing AccessShareLock
then you might also hit the lock manager which could be slow or create
deadlock hazards). It would be much better to pass the Relation
around rather than the OID whenever possible.

I have removed most instances of heap_open() calls after realizing they
were in fact unnecessary (and as you warn possibly hazardous and
performance hogs). Where it's inevitable that we perform heap_open(), I
have changed it so that the correct lockmode is passed.

Also, in 0006:

- I doubt that PartitionTreeNodeData's header comment will survive
contact with pgindent.

Fixed by adding "/* ----" at the top of the comment.

In 0007:

- oid_is_foreign_table could/should do a syscache lookup instead of
opening the heap relation. But actually I think you could drop it
altogether and use get_rel_relkind().

Done with get_rel_relkind().

- The XXX in EndCopy will need to be fixed somehow.

Fixed. Realized that doing that in EndCopy() is kind of pointless, I
moved that code to the end of CopyFrom(), right before a call to
FreeExecutorState.

- I suspect that many of the pfree's in this patch are pointless
because the contexts in which those allocations are performed will be
reset or deleted shortly afterwards anyway. Only pfree data that
might otherwise live substantially longer than we want, because pfree
consumes some cycles.

They do seem to be freeing unnecessarily, removed.

- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.

Agree that it looks horrible. The problem is we don't add partition
(child table) RTEs when planning an insert on the parent and FDW
partitions can't do without some planner handling - planForeignModify()
expects a valid PlannerInfo for deparsing target lists (basically, to be
able to use planner_rt_fetch()).

Any design beside this kludge would perhaps mean that we will be adding
valid partition RTEs at some earlier planning stage much like what
expand_inherited_rtentry() does during non-insert queries. Perhaps, we
could have expand_inherited_rtentry() make an exception in partitioned
tables' case and create root->append_rel_list members even in the insert
case. We could then go over the append_rel_list in make_modifytable() to
get the valid RT index of foreign child tables to pass to
PlanForeignModify(). Using append_rel_list for insert planning is perhaps
equally ugly though. Thoughts?

- I don't understand how it can be right for get_partition_for_tuple()
to be emitting an error that says "range partition key contains null".
A defective partition key should be detected at partition creation
time, not in the middle of tuple routing.

That error is emitted when the partition key of the *input tuple* is found
to contain NULLs. Maybe we need to consider something like how btree
indexes treat NULLs - order NULLs either before or after all the non-NULL
values based on NULLS FIRST/LAST config. Thoughts?

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-15.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-15.patchDownload
From da0f114563a07f6b047f7742b1a553df9b0d3a52 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 52 files changed, 1952 insertions(+), 56 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..1a95219 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-15.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-15.patchDownload
From 95521097d7084b01f549b330badb9a4d1474ec54 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ee1f673..fb92e7f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1226,9 +1226,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2085,6 +2086,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4959,7 +4963,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4973,7 +4977,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5501,7 +5506,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6910,6 +6917,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14262,6 +14310,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14322,6 +14373,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14340,7 +14392,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14557,6 +14610,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 642c4d5..f33f86d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -311,6 +311,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -649,5 +650,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-15.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-15.patchDownload
From 69c6f52cd82d97743d5e493260510192b4a777aa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

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

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

0004-psql-and-pg_dump-support-for-partitions-15.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-15.patchDownload
From b3f478a60a10a36f9a9668af05eda2d81a40dff8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index fb92e7f..57e626c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5608,9 +5608,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5637,6 +5644,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14168,6 +14239,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14196,8 +14278,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14226,7 +14311,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14284,15 +14370,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14462,6 +14555,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f33f86d..dd5ad8f 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -321,6 +321,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -461,6 +463,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-15.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-15.patchDownload
From f9287303e1579d7657a18815fcd4a1a93f58ba01 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   75 +++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 608 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..ea3f59a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * NOTE: SQL specifies that a NULL result from a constraint expression
+	 * is not to be treated as a failure.  Therefore, tell ExecQual to
+	 * return TRUE for NULL.
+	 *
+	 * XXX - although, it's unlikely that NULL would result.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1807,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck)
+	{
+		if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+		{
+			char	   *val_desc;
+
+			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+			modifiedCols = bms_union(insertedCols, updatedCols);
+			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+													 slot,
+													 tupdesc,
+													 modifiedCols,
+													 64);
+			ereport(ERROR,
+					(errcode(ERRCODE_CHECK_VIOLATION),
+					 errmsg("new row for relation \"%s\" violates partition constraint",
+							RelationGetRelationName(rel)),
+			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+		}
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionTreeNode-data-structure-15.patchtext/x-diff; name=0006-Introduce-a-PartitionTreeNode-data-structure-15.patchDownload
From a6de864816146400537b0dc06b5763e451ada216 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure.

It encapsulates the tree structure of a partition hierarchy which can be
arbitrarily deeply nested.  Every node in the tree represents a partitioned
table.  The only currently envisioned application is for tuple-routing.
---
 src/backend/catalog/partition.c |  213 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 +
 2 files changed, 218 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e21e7ad..7bc4b15 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -114,6 +114,64 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/* ----------------
+ * Partition tree node (corresponding to one partitioned table in the
+ * partition tree)
+ *
+ *	pkinfo				PartitionKey executor state
+ *
+ *	pdesc				Info about immediate partitions (see
+ *						PartitionDescData)
+ *
+ *	relid				Table OID
+ *
+ *	index				If the table is partition itself, index in the
+ *						parent's partition array
+ *
+ *	num_leaf_parts		Number of leaf partitions in the partition
+ *						tree rooted at this node
+ *
+ *	offset				Index across the whole partition tree, of the first
+ *						leaf partition in the sub-tree rooted at this node
+ *
+ *	downlink			Link to the leftmost child node (corresponding to
+ *						the first of our partitions that are partitioned
+ *						themselves)
+ *
+ *	next				Link to the right sibling node (corresponding to the
+ *						next partition on the same level that is partitioned
+ *						itself)
+ * ----------------
+ */
+typedef struct PartitionTreeNodeData
+{
+	PartitionKeyExecInfo *pkinfo;
+	PartitionDesc		pdesc;
+	Oid					relid;
+	int					index;
+	int					offset;
+	int					num_leaf_parts;
+
+	struct PartitionTreeNodeData *downlink;
+	struct PartitionTreeNodeData *next;
+} PartitionTreeNodeData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -124,6 +182,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset, int lockmode);
+static int get_leaf_partition_count(PartitionTreeNode parent);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -874,6 +935,55 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/*
+ * RelationGetPartitionTreeNode
+ *		Recursively form the partition node tree with rel at root
+ *
+ * All the partitions will be locked with lockmode unless it is NoLock.
+ */
+PartitionTreeNode
+RelationGetPartitionTreeNode(Relation rel, int lockmode)
+{
+	PartitionTreeNode	root;
+
+	/*
+	 * We recurse to build the PartitionTreeNodes for any partitions in the
+	 * partition tree that are themselves partitioned tables.
+	 */
+	root = GetPartitionTreeNodeRecurse(rel, 0, lockmode);
+	root->index = 0;	/* Root table has no parent */
+	root->num_leaf_parts = get_leaf_partition_count(root);
+
+	return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ * 		Recursively compute the list of OIDs of leaf partitions in the
+ *		partition tree rooted at parent
+ */
+List *
+get_leaf_partition_oids(PartitionTreeNode parent)
+{
+	int		i;
+	List   *result = NIL;
+	PartitionTreeNode node = parent->downlink;
+
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are leaf partitions */
+		if (node && i == node->index)
+		{
+			result = list_concat(result, get_leaf_partition_oids(node));
+			node = node->next;
+		}
+		else
+			result = lappend_oid(result, parent->pdesc->oids[i]);
+	}
+
+	return result;
+}
+
 /* Module-local functions */
 
 /*
@@ -1314,6 +1424,109 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ * GetPartitionTreeNodeRecurse
+ *		Workhorse of RelationGetPartitionTreeNode
+ *
+ * offset is the index across the whole partition tree of the first leaf node
+ * in this subtree.
+ */
+static PartitionTreeNode
+GetPartitionTreeNodeRecurse(Relation rel, int offset, int lockmode)
+{
+	PartitionTreeNode	parent,
+						prev;
+	int					i;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* First build our own node */
+	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
+	parent->pkinfo = NULL;
+	parent->pdesc = RelationGetPartitionDesc(rel);
+	parent->relid = RelationGetRelid(rel);
+	parent->offset = offset;
+	parent->downlink = NULL;
+	parent->next = NULL;
+
+	/*
+	 * Go through rel's partitions and recursively add nodes for partitions
+	 * that are themselves partitioned.  Link parent to the first child node
+	 * using 'downlink'.  Each new child node is linked to its right sibling
+	 * using 'next'.  Offset value passed when creating a child node is
+	 * determined by looking at the left node if one exists or the parent
+	 * node if it is the first child node of this level.
+	 */
+	prev = NULL;
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		Oid			relid = parent->pdesc->oids[i];
+		int			offset;
+		Relation	rel;
+		PartitionTreeNode child;
+
+		rel = heap_open(relid, lockmode);
+
+		/* Skip if a leaf partition */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(rel, NoLock);
+			continue;
+		}
+
+		if (prev)
+			offset = prev->offset + prev->num_leaf_parts +
+												(i - prev->index - 1);
+		else
+			offset = parent->offset + i;
+
+		child = GetPartitionTreeNodeRecurse(rel, offset, lockmode);
+		child->index = i;
+		child->num_leaf_parts = get_leaf_partition_count(child);
+
+		heap_close(rel, NoLock);
+
+		/* Found first child; link to it. */
+		if (parent->downlink == NULL)
+			parent->downlink = child;
+
+		/* Link new node to the left sibling, if any  */
+		if (prev)
+			prev->next = child;
+		prev = child;
+	}
+
+	return parent;
+}
+
+/*
+ * get_leaf_partition_count
+ * 		Recursively count the number of leaf partitions in the partition
+ *		tree rooted at parent
+ */
+static int
+get_leaf_partition_count(PartitionTreeNode parent)
+{
+	int		i;
+	int 	result = 0;
+	PartitionTreeNode node = parent->downlink;
+
+	for (i = 0; i < parent->pdesc->nparts; i++)
+	{
+		/* Indexes 0..(node->index - 1) are of leaf partitions */
+		if (node && i == node->index)
+		{
+			result += get_leaf_partition_count(node);
+			node = node->next;
+		}
+		else
+			result += 1;
+	}
+
+	return result;
+}
+
 /* List partition related support functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 062de88..672183b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel, int lockmode);
+extern List *get_leaf_partition_oids(PartitionTreeNode parent);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables-15.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-15.patchDownload
From 23f29fd7b63fffa0e1fc2c65c5883f262591d848 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT.
---
 src/backend/catalog/partition.c         |  317 ++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c             |  222 +++++++++++++++++++++-
 src/backend/commands/tablecmds.c        |    1 +
 src/backend/executor/execMain.c         |   59 ++++++-
 src/backend/executor/nodeModifyTable.c  |  160 ++++++++++++++++
 src/backend/nodes/copyfuncs.c           |    1 +
 src/backend/nodes/outfuncs.c            |    1 +
 src/backend/nodes/readfuncs.c           |    1 +
 src/backend/optimizer/plan/createplan.c |   76 ++++++++
 src/backend/parser/analyze.c            |    8 +
 src/include/catalog/partition.h         |    7 +
 src/include/executor/executor.h         |    6 +
 src/include/nodes/execnodes.h           |   10 +
 src/include/nodes/plannodes.h           |    1 +
 src/test/regress/expected/insert.out    |   52 +++++
 src/test/regress/sql/insert.sql         |   25 +++
 16 files changed, 940 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7bc4b15..4e253c1 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -185,6 +185,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
 static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset, int lockmode);
 static int get_leaf_partition_count(PartitionTreeNode parent);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -198,6 +210,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1443,7 +1457,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset, int lockmode)
 
 	/* First build our own node */
 	parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData));
-	parent->pkinfo = NULL;
+	parent->pkinfo = BuildPartitionKeyExecInfo(rel);
 	parent->pdesc = RelationGetPartitionDesc(rel);
 	parent->relid = RelationGetRelid(rel);
 	parent->offset = offset;
@@ -1527,6 +1541,273 @@ get_leaf_partition_count(PartitionTreeNode parent)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Recursively finds the "leaf" partition for tuple
+ *
+ * parent is the root of the partition tree through which we are trying
+ * to route the tuple contained in *slot.
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionTreeNode parent,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionKeyExecInfo   *pkinfo = parent->pkinfo;
+	PartitionTreeNode		node,
+							prev;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		i;
+	int		cur_idx;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	if (parent->pdesc->nparts == 0)
+	{
+		*failed_at = parent->relid;
+		return -1;
+	}
+
+	/* Extract partition key from tuple */
+	FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+	switch (pkinfo->pi_Key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			cur_idx = list_partition_for_tuple(pkinfo->pi_Key, parent->pdesc,
+											   values[0], isnull[0]);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			/* Disallow nulls in the partition key of the tuple */
+			for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+				if (isnull[i])
+					ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key of row contains null")));
+
+			cur_idx = range_partition_for_tuple(pkinfo->pi_Key, parent->pdesc,
+												values);
+			break;
+	}
+
+	/* No partition found at this level */
+	if (cur_idx < 0)
+	{
+		*failed_at = parent->relid;
+		return cur_idx;
+	}
+
+	/*
+	 * The partition we found may either be a leaf partition or partitioned
+	 * itself.  In the latter case, we need to recurse by finding the proper
+	 * node (ie, such that node->index == cur_idx) to pass down.
+	 */
+	prev = parent;
+	node = parent->downlink;
+	while (node != NULL)
+	{
+		if (node->index >= cur_idx)
+			break;
+
+		prev = node;
+		node = node->next;
+	}
+
+	/*
+	 * If we couldn't find a node, that means cur_idx is actually a leaf
+	 * partition.  In the simpler case where all of the left siblings are leaf
+	 * partitions themselves, we can get the correct index to return by adding
+	 * cur_idx to the parent node's offset.  Otherwise, we need to compensate
+	 * for the leaf partitions in the partition subtrees of all the left
+	 * siblings that are partitioned, which, fortunately it suffices to look
+	 * at the rightmost such node.
+	 */
+	Assert(prev != NULL);
+	if (!node || cur_idx < node->index)
+		return prev == parent ? prev->offset + cur_idx
+							  : prev->offset + prev->num_leaf_parts +
+								(cur_idx - prev->index - 1);
+
+	/*
+	 * Must recurse because it turns out that the partition at cur_idx is
+	 * partitioned itself
+	 */
+	Assert (node != NULL && node->index == cur_idx);
+
+	return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple (arg 'value' contains the
+ *		list partition key of the original tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Get the index of the range partition for a tuple (arg 'tuple'
+ *		actually contains the range partition key of the original
+ *		tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be less
+	 * or equal with the tuple.  That is, the tuple belongs to the partition
+	 * with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter is
+	 * indeed an upper (!lower) bound.  If it turns out otherwise, the
+	 * corresponding index will be -1, which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1772,6 +2053,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/* If datums are equal and this is an upper bound, tuple > bound */
+	if (cmpval == 0 && !bound->lower)
+		return -1;
+
+	return cmpval;
+}
+
+/*
  * Return whether two range bounds are equal simply by comparing datums
  */
 static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..cb0ca0e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionTreeNode		partition_tree;	/* partition node tree */
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
+	List				   *partition_fdw_priv_lists;
+	int						num_partitions;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1403,91 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i;
+			int				num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+			PlannerInfo *root = makeNode(PlannerInfo);	/* mostly dummy */
+			ModifyTable *plan = makeNode(ModifyTable);	/* ditto */
+			RangeTblEntry *fdw_rte = makeNode(RangeTblEntry);	/* ditto */
+			List		*fdw_private_lists = NIL;
+
+			/* Form the partition node tree and lock partitions */
+			cstate->partition_tree = RelationGetPartitionTreeNode(rel,
+														  RowExclusiveLock);
+			leaf_parts = get_leaf_partition_oids(cstate->partition_tree);
+			num_leaf_parts = list_length(leaf_parts);
+
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *)
+								palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			/* For use below, iff a partition found to be a foreign table */
+			plan->operation = CMD_INSERT;
+			plan->plans = list_make1(makeNode(Result));
+			fdw_rte->rtekind = RTE_RELATION;
+			fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+			root->parse = makeNode(Query);
+			root->parse->rtable = list_make1(fdw_rte);
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	part_rel;
+
+				part_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(part_rel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  part_rel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				/* Special dance for foreign tables */
+				if (leaf_part_rri->ri_FdwRoutine)
+				{
+					List		  *fdw_private;
+
+					fdw_rte->relid = RelationGetRelid(part_rel);
+					fdw_private = leaf_part_rri->ri_FdwRoutine->PlanForeignModify(root,
+																		  plan,
+																		  1,
+																		  0);
+					fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+				}
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(part_rel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(part_rel),
+									gettext_noop("could not convert row type"));
+
+				leaf_part_rri++;
+				i++;
+			}
+
+			cstate->partition_fdw_priv_lists = fdw_private_lists;
+		}
 	}
 	else
 	{
@@ -2255,6 +2346,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2373,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2484,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2512,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_tree != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2439,10 +2534,46 @@ CopyFrom(CopyState cstate)
 	 */
 	ExecBSInsertTriggers(estate, resultRelInfo);
 
+	/* Initialize FDW partition insert plans */
+	if (cstate->partition_tree)
+	{
+		int			i,
+					j;
+		List	   *fdw_private_lists = cstate->partition_fdw_priv_lists;
+		ModifyTableState   *mtstate = makeNode(ModifyTableState);
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Mostly dummy containing enough state for BeginForeignModify */
+		mtstate->ps.state = estate;
+		mtstate->operation = CMD_INSERT;
+
+		j = 0;
+		leaf_part_rri = cstate->partitions;
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				List *fdw_private;
+
+				Assert(fdw_private_lists);
+				fdw_private = list_nth(fdw_private_lists, j++);
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+															leaf_part_rri,
+															fdw_private,
+															0, 0);
+			}
+			leaf_part_rri++;
+		}
+	}
+
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2625,50 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_tree)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+												cstate->partition_tree,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2523,7 +2698,16 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
-				if (useHeapMultiInsert)
+				if (resultRelInfo->ri_FdwRoutine)
+				{
+					resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+																resultRelInfo,
+																	slot,
+																	NULL);
+					/* AFTER ROW INSERT Triggers */
+					ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+				}
+				else if (useHeapMultiInsert)
 				{
 					/* Add this tuple to the tuple buffer */
 					if (nBufferedTuples == 0)
@@ -2553,7 +2737,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2762,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2781,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2806,28 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/*
+	 * Close all partitions and indices thereof, also clean up any
+	 * state of FDW modify plans.
+	 */
+	if (cstate->partition_tree)
+	{
+		int		i;
+
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+			if (resultRelInfo->ri_FdwRoutine &&
+				resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+				resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+														   resultRelInfo);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3b72ae3..6478211 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ea3f59a..3b4dd38 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3001,53 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode parent,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(parent, slot, estate, &failed_at);
+
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..3523a2d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,50 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_tree)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+											mtstate->mt_partition_tree,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +556,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1616,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1707,100 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		mtstate->mt_partition_tree = RelationGetPartitionTreeNode(rel,
+														RowExclusiveLock);
+		leaf_parts = get_leaf_partition_oids(mtstate->mt_partition_tree);
+		num_leaf_parts = list_length(leaf_parts);
+
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (leaf_part_rri->ri_FdwRoutine)
+			{
+				ListCell    *lc;
+				List	    *fdw_private;
+				int			 k;
+
+				/*
+				 * There are as many fdw_private's in fdwPrivLists as there
+				 * are FDW partitions, but we must find the intended for the
+				 * this foreign table.
+				 */
+				k = 0;
+				foreach(lc, node->fdwPartitionOids)
+				{
+					if (lfirst_oid(lc) == ftoid)
+						break;
+					k++;
+				}
+
+				Assert(k < num_leaf_parts);
+				fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+				Assert(fdw_private != NIL);
+
+				leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+																leaf_part_rri,
+																fdw_private,
+																0,
+																eflags);
+				j++;
+			}
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2118,20 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+		if (resultRelInfo->ri_FdwRoutine &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(fdwPrivLists);
+	COPY_NODE_FIELD(fdwPartitionOids);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
+	WRITE_NODE_FIELD(fdwPartitionOids);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(fdwPrivLists);
+	READ_NODE_FIELD(fdwPartitionOids);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..caf2f44 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
@@ -6159,6 +6160,81 @@ make_modifytable(PlannerInfo *root,
 	node->fdwPrivLists = fdw_private_list;
 	node->fdwDirectModifyPlans = direct_modify_plans;
 
+	/* Collect insert plans for all FDW-managed partitions */
+	if (node->operation == CMD_INSERT)
+	{
+		RangeTblEntry  *rte,
+					  **saved_simple_rte_array;
+		List		   *partition_oids,
+					   *fdw_partition_oids;
+
+		Assert(list_length(resultRelations) == 1);
+		rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+		Assert(rte->rtekind == RTE_RELATION);
+
+		if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+			return node;
+
+		partition_oids = find_all_inheritors(rte->relid, RowExclusiveLock,
+											 NULL);
+
+		/* Discard any previous content which is useless anyway */
+		fdw_private_list = NIL;
+		fdw_partition_oids = NIL;
+
+		/*
+		 * To force the FDW driver fetch the intended RTE, we need to temporarily
+		 * switch root->simple_rte_array to the one holding only that RTE at a
+		 * designated index, for every foreign table.
+		 */
+		saved_simple_rte_array = root->simple_rte_array;
+		root->simple_rte_array = (RangeTblEntry **)
+										palloc0(2 * sizeof(RangeTblEntry *));
+		foreach(lc, partition_oids)
+		{
+			Oid		myoid = lfirst_oid(lc);
+			FdwRoutine *fdwroutine;
+			List	   *fdw_private;
+
+			/*
+			 * We are only interested in foreign tables.  Note that this will
+			 * also eliminate any partitioned tables since foreign tables can
+			 * only ever be leaf partitions.
+			 */
+			if (get_rel_relkind(myoid) != RELKIND_FOREIGN_TABLE)
+				continue;
+
+			fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+			fdwroutine = GetFdwRoutineByRelId(myoid);
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+			{
+				RangeTblEntry *fdw_rte;
+
+				fdw_rte = copyObject(rte);
+				fdw_rte->relid = myoid;
+				fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+				/*
+				 * Assumes PlanForeignModify() uses planner_rt_fetch(), also,
+				 * see the above comment.
+				 */
+				root->simple_rte_array[1] = fdw_rte;
+
+				fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+			}
+			else
+				fdw_private = NIL;
+
+			fdw_private_list = lappend(fdw_private_list, fdw_private);
+		}
+
+		root->simple_rte_array = saved_simple_rte_array;
+
+		node->fdwPrivLists = fdw_private_list;
+		node->fdwPartitionOids = fdw_partition_oids;
+	}
+
 	return node;
 }
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 672183b..e1ccaf8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -50,4 +52,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel, int lockmode);
 extern List *get_leaf_partition_oids(PartitionTreeNode parent);
+
+extern int get_partition_for_tuple(PartitionTreeNode parent,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..fadc402 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionTreeNode parent,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..06675bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionTreeNodeData *mt_partition_tree;
+										/* Partition descriptor node tree */
+	ResultRelInfo  *mt_partitions;		/* Per leaf partition target
+										 * relations */
+	TupleConversionMap **mt_partition_tupconv_maps;
+										/* Per leaf partition
+										 * tuple conversion map */
+	int				mt_num_partitions;	/* Number of leaf partition target
+										 * relations in the above array */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
+	List	   *fdwPartitionOids;	/* OIDs of FDW-managed partition */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-15.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-15.patchDownload
From 2e5cbf2dadbdd4c7bc389e9fdc03f80705d21673 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#133Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#132)
Re: Declarative partitioning - another take

On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

OK, I will share the performance results soon.

Thanks.

Also, in 0006:

- I doubt that PartitionTreeNodeData's header comment will survive
contact with pgindent.

Fixed by adding "/* ----" at the top of the comment.

OK. Hopefully you also tested that. In general, with a patch set
this large, the more you can do to make it pgindent-clean, the better.
Ideally none of your code would get changed by pgindent, or at least
not the new files. But note that you will probably have have to
update typedefs.list if you actually want to be able to run it without
having it mangle things that involve your new structs.

- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.

Agree that it looks horrible. The problem is we don't add partition
(child table) RTEs when planning an insert on the parent and FDW
partitions can't do without some planner handling - planForeignModify()
expects a valid PlannerInfo for deparsing target lists (basically, to be
able to use planner_rt_fetch()).

Any design beside this kludge would perhaps mean that we will be adding
valid partition RTEs at some earlier planning stage much like what
expand_inherited_rtentry() does during non-insert queries. Perhaps, we
could have expand_inherited_rtentry() make an exception in partitioned
tables' case and create root->append_rel_list members even in the insert
case. We could then go over the append_rel_list in make_modifytable() to
get the valid RT index of foreign child tables to pass to
PlanForeignModify(). Using append_rel_list for insert planning is perhaps
equally ugly though. Thoughts?

If it's only needed for foreign tables, how about for v1 we just throw
an error and say errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table") for now. We
can come back and fix it later. Doing more inheritance expansion
early is probably not a good idea because it likely sucks for
performance, and that's especially unfortunate if it's only needed for
foreign tables. Coming up with some new FDW API or some modification
to the existing one is probably better, but I don't really want to get
hung up on that right now.

- I don't understand how it can be right for get_partition_for_tuple()
to be emitting an error that says "range partition key contains null".
A defective partition key should be detected at partition creation
time, not in the middle of tuple routing.

That error is emitted when the partition key of the *input tuple* is found
to contain NULLs. Maybe we need to consider something like how btree
indexes treat NULLs - order NULLs either before or after all the non-NULL
values based on NULLS FIRST/LAST config. Thoughts?

Well, I'd say my confusion suggests that the error message needs to be
clearer about what the problem is. I think this is basically a case
of there being no partition for the given row, so maybe just arrange
to use that same message here ("no partition of relation \"%s\" found
for row").

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

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

#134Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#132)
Re: Declarative partitioning - another take

On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Meanwhile, here are updated patch that address most of the following comments.

OK, I have re-reviewed 0005 and it looks basically fine to me, modulo
a few minor nitpicks. "This is called, *iff*" shouldn't have a comma
there, and I think the entire comment block that starts with "NOTE:
SQL specifies that a NULL" and ends with "it's unlikely that NULL
would result." should be changed to say something like /* As for
catalogued constraints, we treat a NULL result as a success, not a
failure. */ rather than duplicating an existing comment that doesn't
quite apply here. Finally, ExecConstraints() contains a new if-block
whose sole contents are another if-block. Perhaps if (this && that)
would be better.

Regarding 0006 and 0007, I think the PartitionTreeNodeData structure
you've chosen is awfully convoluted and probably not that efficient.
For example, get_partition_for_tuple() contains this loop:

+       prev = parent;
+       node = parent->downlink;
+       while (node != NULL)
+       {
+               if (node->index >= cur_idx)
+                       break;
+
+               prev = node;
+               node = node->next;
+       }

Well, it looks to me like that's an O(n) way to find the n'th
partition, which seems like a pretty bad idea in performance-critical
code, which this is. I think this whole structure needs a pretty
heavy overhaul. Here's a proposal:

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that
are partitioned be K. Assign each partitioned table in the hierarchy
an index between 0 and K-1. Make your top level data structure (in
lieu of PartitionTreeNodeData) be an array of K PartitionDispatch
objects, with the partitioning root in entry 0 and the rest in the
remaining entries.

2. Within each PartitionDispatch object, store (a) a pointer to a
PartitionDesc and (b) an array of integers of length equal to the
PartitionDesc's nparts value. Each integer i, if non-negative, is the
final return value for get_partition_for_tuple. If i == -1, tuple
routing fails. If i < -1, we must next route using the subpartition
whose PartitionDesc is at index -(i+1). Arrange for the array to be
in the same order the PartitionDesc's OID list.

3. Now get_partition_for_tuple looks something like this:

K = 0
loop:
pd = PartitionDispatch[K]
idx = list/range_partition_for_tuple(pd->partdesc, ...)
if (idx >= -1)
return idx
K = -(idx + 1)

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

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

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

#135Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#133)
Re: Declarative partitioning - another take

On 2016/11/18 1:43, Robert Haas wrote:

On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote wrote:

OK, I will share the performance results soon.

Thanks.

Also, in 0006:

- I doubt that PartitionTreeNodeData's header comment will survive
contact with pgindent.

Fixed by adding "/* ----" at the top of the comment.

OK. Hopefully you also tested that. In general, with a patch set
this large, the more you can do to make it pgindent-clean, the better.
Ideally none of your code would get changed by pgindent, or at least
not the new files. But note that you will probably have have to
update typedefs.list if you actually want to be able to run it without
having it mangle things that involve your new structs.

OK, I was afraid that running pgindent might end up introducing unrelated
diffs in the patch, but I'm sure any code committed to HEAD would have
been through pgindent and my worry might be pointless after all. Also,
thanks for the tip about the typedefs.list. I will go try pgindent'ing
the patches.

- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.

Agree that it looks horrible. The problem is we don't add partition
(child table) RTEs when planning an insert on the parent and FDW
partitions can't do without some planner handling - planForeignModify()
expects a valid PlannerInfo for deparsing target lists (basically, to be
able to use planner_rt_fetch()).

Any design beside this kludge would perhaps mean that we will be adding
valid partition RTEs at some earlier planning stage much like what
expand_inherited_rtentry() does during non-insert queries. Perhaps, we
could have expand_inherited_rtentry() make an exception in partitioned
tables' case and create root->append_rel_list members even in the insert
case. We could then go over the append_rel_list in make_modifytable() to
get the valid RT index of foreign child tables to pass to
PlanForeignModify(). Using append_rel_list for insert planning is perhaps
equally ugly though. Thoughts?

If it's only needed for foreign tables, how about for v1 we just throw
an error and say errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table") for now. We
can come back and fix it later. Doing more inheritance expansion
early is probably not a good idea because it likely sucks for
performance, and that's especially unfortunate if it's only needed for
foreign tables. Coming up with some new FDW API or some modification
to the existing one is probably better, but I don't really want to get
hung up on that right now.

OK, I agree with the decision to make tuple-routing a unsupported feature
in the first cut as far as foreign partitions are concerned. Once can
still insert data into partitions directly.

I am assuming that the error should be thrown all the way below
ExecInsert(), not in the planner. Which means I should revert any changes
I've made to the planner in this patch.

- I don't understand how it can be right for get_partition_for_tuple()
to be emitting an error that says "range partition key contains null".
A defective partition key should be detected at partition creation
time, not in the middle of tuple routing.

That error is emitted when the partition key of the *input tuple* is found
to contain NULLs. Maybe we need to consider something like how btree
indexes treat NULLs - order NULLs either before or after all the non-NULL
values based on NULLS FIRST/LAST config. Thoughts?

Well, I'd say my confusion suggests that the error message needs to be
clearer about what the problem is. I think this is basically a case
of there being no partition for the given row, so maybe just arrange
to use that same message here ("no partition of relation \"%s\" found
for row").

The reason NULLs in an input row are caught and rejected (with the current
message) before control reaches range_partition_for_tuple() is because
it's not clear to me whether the range bound comparison logic in
partition_rbound_datum_cmp() should be prepared to handle NULLs and what
the results of comparisons should look like. Currently, all it ever
expects to see in the input tuple's partition key is non-NULL datums.
Comparison proceeds as follows: if a range bound datum is a finite value,
we invoke the comparison proc or if it is infinite, we conclude that the
input tuple is > or < the bound in question based on whether the bound is
a lower or upper bound, respectively.

Or are you saying that get_tuple_for_partition() should simply return -1
(partition not found) in case of encountering a NULL in range partition
key to the caller instead of throwing error as is now? If the user sees
the message and decides to create a new range partition that *will* accept
such a row, how do they decide what its boundaries should be?

Thanks,
Amit

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

#136Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#134)
8 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/18 4:14, Robert Haas wrote:

On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Meanwhile, here are updated patch that address most of the following comments.

OK, I have re-reviewed 0005 and it looks basically fine to me, modulo
a few minor nitpicks. "This is called, *iff*" shouldn't have a comma
there,

Fixed.

and I think the entire comment block that starts with "NOTE:
SQL specifies that a NULL" and ends with "it's unlikely that NULL
would result." should be changed to say something like /* As for
catalogued constraints, we treat a NULL result as a success, not a
failure. */ rather than duplicating an existing comment that doesn't
quite apply here.

Ah, you're right that the comment does not apply as it is. I rewrote that
comment.

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message about
NULLs in the range partition key)

Finally, ExecConstraints() contains a new if-block
whose sole contents are another if-block. Perhaps if (this && that)
would be better.

Agreed, should have noticed that.

Regarding 0006 and 0007, I think the PartitionTreeNodeData structure
you've chosen is awfully convoluted and probably not that efficient.
For example, get_partition_for_tuple() contains this loop:

+       prev = parent;
+       node = parent->downlink;
+       while (node != NULL)
+       {
+               if (node->index >= cur_idx)
+                       break;
+
+               prev = node;
+               node = node->next;
+       }

Well, it looks to me like that's an O(n) way to find the n'th
partition, which seems like a pretty bad idea in performance-critical
code, which this is. I think this whole structure needs a pretty
heavy overhaul. Here's a proposal:

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that
are partitioned be K. Assign each partitioned table in the hierarchy
an index between 0 and K-1. Make your top level data structure (in
lieu of PartitionTreeNodeData) be an array of K PartitionDispatch
objects, with the partitioning root in entry 0 and the rest in the
remaining entries.

2. Within each PartitionDispatch object, store (a) a pointer to a
PartitionDesc and (b) an array of integers of length equal to the
PartitionDesc's nparts value. Each integer i, if non-negative, is the
final return value for get_partition_for_tuple. If i == -1, tuple
routing fails. If i < -1, we must next route using the subpartition
whose PartitionDesc is at index -(i+1). Arrange for the array to be
in the same order the PartitionDesc's OID list.

3. Now get_partition_for_tuple looks something like this:

K = 0
loop:
pd = PartitionDispatch[K]
idx = list/range_partition_for_tuple(pd->partdesc, ...)
if (idx >= -1)
return idx
K = -(idx + 1)

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

PS: I haven't run the patches through pgindent yet.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-16.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-16.patchDownload
From 71736b164509937fcc78078682242a0d66c541d3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/8] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 52 files changed, 1952 insertions(+), 56 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..1a95219 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-16.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-16.patchDownload
From bf32e652ac0690ecf170b989f1e53ab1b0e13f1b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9f59f53..d32d0ff 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1228,9 +1228,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2087,6 +2088,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4961,7 +4965,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4975,7 +4979,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5503,7 +5508,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6902,6 +6909,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14311,6 +14359,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14371,6 +14422,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14389,7 +14441,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14606,6 +14659,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f3e5977..e9849ef 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -312,6 +312,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-16.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-16.patchDownload
From 5162cf0af22829793f8ca327f9904f5e08fe23b5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

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

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

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

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

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

0004-psql-and-pg_dump-support-for-partitions-16.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-16.patchDownload
From 75cc79d0e97a95d131ed258c7a8a39d75cecff49 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/8] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d32d0ff..467c243 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5610,9 +5610,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5639,6 +5646,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14217,6 +14288,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14245,8 +14327,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14275,7 +14360,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14333,15 +14419,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14511,6 +14604,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e9849ef..a7cb00a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -322,6 +322,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-16.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-16.patchDownload
From 624d7ff4e75616b0486fbda85550542214f6dbee Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/8] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   69 ++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 602 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..c7a6347 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1746,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1779,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1803,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index adc1fd7..bdb4e2c 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 5637c68..5392fa5 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Introduce-a-PartitionDispatch-data-structure-16.patchtext/x-diff; name=0006-Introduce-a-PartitionDispatch-data-structure-16.patchDownload
From fb0e89559b0b19e9c15bcdaef6bf118d0cf57b98 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/8] Introduce a PartitionDispatch data structure.

Each partitioned table in a partition tree gets one and contains info
such as a pointer to its partition descriptor, partition key execution
state, global sequence numbers of its leaf partitions and a way to link
to the PartitionDispatch objects of any of its partitions that are
partitioned themselves.

Starting with the PartitionDispatch object of the root partitioned table
and a tuple to route, one can get the global sequence number of the leaf
partition that the tuple gets routed to, if one exists.
---
 src/backend/catalog/partition.c |  145 +++++++++++++++++++++++++++++++++++++++
 src/include/catalog/partition.h |    5 ++
 2 files changed, 150 insertions(+), 0 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e21e7ad..03c7fa1 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -114,6 +114,43 @@ typedef struct PartitionListValue
 	int		index;
 } PartitionListValue;
 
+/*---------------------
+ * PartitionKeyExecInfo
+ *
+ *		This struct holds the information needed to extract partition
+ *		column values from a heap tuple.
+ *
+ *		Key					copy of the rd_partkey of rel
+ *		ExpressionState		exec state for expressions, or NIL if none
+ *---------------------
+ */
+typedef struct PartitionKeyExecInfo
+{
+	NodeTag			type;
+	PartitionKey	pi_Key;
+	List		   *pi_ExpressionState;	/* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*-----------------------
+ * PartitionDispatch - information about one partitioned table in a partition
+ * hiearchy required to route a tuple to one of its partitions
+ *
+ *	relid		OID of the table
+ *	pkinfo		Partition key execution state required for expressions
+ *	partdesc	Partition descriptor of the table
+ *	indexes		Array with partdesc->nparts members (for details on what
+ *				individual members represent, see how they are set in
+ *				RelationGetPartitionDispatchInfo())
+ *-----------------------
+ */
+typedef struct PartitionDispatchData
+{
+	Oid						relid;
+	PartitionKeyExecInfo   *pkinfo;
+	PartitionDesc			partdesc;
+	int					   *indexes;
+} PartitionDispatchData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -874,6 +911,114 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/* Turn an array of OIDs with N elements into a list */
+#define OID_ARRAY_TO_LIST(arr, N, list) \
+	do\
+	{\
+		int		i;\
+		for (i = 0; i < (N); i++)\
+			(list) = lappend_oid((list), (arr)[i]);\
+	} while(0)
+
+/*
+ * RelationGetPartitionDispatchInfo
+ *		Returns information necessary to route tuples down a partition tree
+ *
+ * All the partitions will be locked with lockmode, unless it is NoLock.
+ * A list of the OIDs of all the leaf partition of rel is returned in
+ * *leaf_part_oids.
+ */
+PartitionDispatch *
+RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids)
+{
+	PartitionDesc	rootpartdesc = RelationGetPartitionDesc(rel);
+	PartitionDispatchData **pd;
+	List	   *all_parts = NIL,
+			   *parted_rels = NIL;
+	ListCell   *lc;
+	int			i,
+				k,
+				num_parted;
+
+	/*
+	 * Lock partitions and collect OIDs of the partitioned ones to prepare
+	 * their PartitionDispatch objects.
+	 *
+	 * Cannot use find_all_inheritors() here, because then the order of OIDs
+	 * in parted_rels list would be unknown, which does not help because down
+	 * below, we assign indexes within individual PartitionDispatch in an
+	 * order that's predetermined (determined by the order of OIDs in
+	 * individual partition descriptors).
+	 */
+	parted_rels = lappend_oid(parted_rels, RelationGetRelid(rel));
+	num_parted = 1;
+	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
+	foreach(lc, all_parts)
+	{
+		Relation		partrel = heap_open(lfirst_oid(lc), lockmode);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+
+		/*
+		 * If this partition is a partitined table, add its children to to the
+		 * end of the list, so that they are processed as well.
+		 */
+		if (partdesc)
+		{
+			num_parted++;
+			parted_rels = lappend_oid(parted_rels, lfirst_oid(lc));
+			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+		}
+
+		heap_close(partrel, NoLock);
+	}
+
+	/* Generate PartitionDispatch objects for all partitioned tables */
+	pd = (PartitionDispatchData **) palloc(num_parted *
+										sizeof(PartitionDispatchData *));
+	*leaf_part_oids = NIL;
+	i = k = 0;
+	foreach(lc, parted_rels)
+	{
+		/* We locked all partitions above */
+		Relation	partrel = heap_open(lfirst_oid(lc), NoLock);
+		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
+		int			j,
+					m;
+
+		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+		pd[i]->relid = RelationGetRelid(partrel);
+		pd[i]->pkinfo = NULL;
+		pd[i]->partdesc = partdesc;
+		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
+		heap_close(partrel, NoLock);
+
+		m = 0;
+		for (j = 0; j < partdesc->nparts; j++)
+		{
+			Oid		partrelid = partdesc->oids[j];
+
+			if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
+			{
+				*leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
+				pd[i]->indexes[j] = k++;
+			}
+			else
+			{
+				/*
+				 * We can assign indexes this way because of the way
+				 * parted_rels has been generated.
+				 */
+				pd[i]->indexes[j] = -(i + 1 + m);
+				m++;
+			}
+		}
+		i++;
+	}
+
+	return pd;
+}
+
 /* Module-local functions */
 
 /*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 062de88..fb43cc1 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionDispatchData *PartitionDispatch;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids);
 #endif   /* PARTITION_H */
-- 
1.7.1

0007-Tuple-routing-for-partitioned-tables-16.patchtext/x-diff; name=0007-Tuple-routing-for-partitioned-tables-16.patchDownload
From dc82608078d14bffc6c2e174100fbe8b78dcb6fa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 7/8] Tuple routing for partitioned tables.

Both COPY FROM and INSERT are covered by this commit.  Routing to foreing
partitions is not supported at the moment.
---
 src/backend/catalog/partition.c        |  289 +++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c            |  151 ++++++++++++++++-
 src/backend/commands/tablecmds.c       |    1 +
 src/backend/executor/execMain.c        |   58 ++++++-
 src/backend/executor/nodeModifyTable.c |  130 ++++++++++++++
 src/backend/parser/analyze.c           |    8 +
 src/include/catalog/partition.h        |    6 +
 src/include/executor/executor.h        |    6 +
 src/include/nodes/execnodes.h          |    8 +
 src/test/regress/expected/insert.out   |   52 ++++++
 src/test/regress/sql/insert.sql        |   25 +++
 11 files changed, 728 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 03c7fa1..d758500 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -161,6 +161,18 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
 
 static List *generate_partition_qual(Relation rel, bool recurse);
 
+/* Support get_partition_for_tuple() */
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+							Datum *tuple);
+
 /* List partition related support functions */
 static bool equal_list_info(PartitionKey key,
 				PartitionListInfo *l1, PartitionListInfo *l2);
@@ -174,6 +186,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
 static bool equal_range_info(PartitionKey key,
 				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
 static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg);
 static bool partition_rbound_eq(PartitionKey key,
 					PartitionRangeBound *b1, PartitionRangeBound *b2);
 typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -988,7 +1002,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 
 		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
 		pd[i]->relid = RelationGetRelid(partrel);
-		pd[i]->pkinfo = NULL;
+		pd[i]->pkinfo = BuildPartitionKeyExecInfo(partrel);
 		pd[i]->partdesc = partdesc;
 		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
 		heap_close(partrel, NoLock);
@@ -1459,6 +1473,245 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/*
+ *	BuildPartitionKeyExecInfo
+ *		Construct a list of PartitionKeyExecInfo records for an open
+ *		relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key.  Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ *
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+	PartitionKeyExecInfo   *pkinfo;
+
+	pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+	pkinfo->pi_Key = RelationGetPartitionKey(rel);
+	pkinfo->pi_ExpressionState = NIL;
+
+	return pkinfo;
+}
+
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pkinfo->pi_ExpressionState = (List *)
+			ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+							estate);
+	}
+
+	partexpr_item = list_head(pkinfo->pi_ExpressionState);
+	for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+	{
+		AttrNumber	keycol = pkinfo->pi_Key->partattrs[i];
+		Datum		pkDatum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			pkDatum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = pkDatum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Finds a leaf partition for tuple contained in *slot
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionDispatch *pd,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionDispatch parent;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		cur_idx;
+	int		i;
+
+	/* start with the root partitioned table */
+	parent = pd[0];
+	while(1)
+	{
+		PartitionDesc			partdesc = parent->partdesc;
+		PartitionKeyExecInfo   *pkinfo = parent->pkinfo;
+
+		/* Quick exit */
+		if (partdesc->nparts == 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+
+		/* Extract partition key from tuple */
+		FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+		switch (pkinfo->pi_Key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				cur_idx = list_partition_for_tuple(pkinfo->pi_Key, partdesc,
+												   values[0], isnull[0]);
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				/* Disallow nulls in the partition key of the tuple */
+				for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+					if (isnull[i])
+						ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("range partition key of row contains null")));
+
+				cur_idx = range_partition_for_tuple(pkinfo->pi_Key, partdesc,
+													values);
+				break;
+		}
+
+		/*
+		 * cur_idx < 0 means we failed to find a partition of this parent.
+		 * cur_idx >= 0 means we either found the leaf partition we have been
+		 * looking for, or the next parent to find a partition of.
+		 */
+		if (cur_idx < 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+		else if (parent->indexes[cur_idx] < 0)
+			parent = pd[-parent->indexes[cur_idx]];
+		else
+			break;
+	}
+
+	return parent->indexes[cur_idx];
+}
+
+/*
+ * list_partition_for_tuple
+ *		Find the list partition for a tuple (arg 'value' contains the
+ *		list partition key of the original tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+						 Datum value, bool isnull)
+{
+	PartitionListInfo	listinfo;
+	int			found;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+	listinfo = pdesc->boundinfo->bounds.lists;
+
+	if (isnull && listinfo.has_null)
+		return listinfo.null_index;
+	else if (!isnull)
+	{
+		found = partition_list_values_bsearch(key,
+											  listinfo.values,
+											  listinfo.nvalues,
+											  value);
+		if (found >= 0)
+			return listinfo.indexes[found];
+	}
+
+	/* Control reaches here if isnull and !listinfo->has_null */
+	return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ *		Get the index of the range partition for a tuple (arg 'tuple'
+ *		actually contains the range partition key of the original
+ *		tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+	int			offset;
+	PartitionRangeInfo	rangeinfo;
+
+	Assert(pdesc->nparts > 0);
+	Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+	rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+	offset = partition_rbound_bsearch(key,
+									  rangeinfo.bounds, rangeinfo.nbounds,
+									  tuple, partition_rbound_datum_cmp,
+									  true, NULL);
+
+	/*
+	 * Offset returned is such that the bound at offset is found to be less
+	 * or equal with the tuple.  That is, the tuple belongs to the partition
+	 * with the rangeinfo.bounds[offset] as the lower bound and
+	 * rangeinfo.bounds[offset+1] as the upper bound, provided the latter is
+	 * indeed an upper (!lower) bound.  If it turns out otherwise, the
+	 * corresponding index will be -1, which means no valid partition exists.
+	 */
+	return rangeinfo.indexes[offset+1];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1704,6 +1957,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
 }
 
 /*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+						   void *arg)
+{
+	Datum  *datums1 = bound->datums,
+		   *datums2 = (Datum *) arg;
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (bound->infinite[i])
+			return bound->lower ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/* If datums are equal and this is an upper bound, tuple > bound */
+	if (cmpval == 0 && !bound->lower)
+		return -1;
+
+	return cmpval;
+}
+
+/*
  * Return whether two range bounds are equal simply by comparing datums
  */
 static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..7d76ead 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionDispatch	   *partition_dispatch_info;
+	int						num_partitions;
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1401,67 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDispatch *pd;
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i,
+							num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+
+			/* Get the tuple-routing information and lock partitions */
+			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+												  &leaf_parts);
+			num_leaf_parts = list_length(leaf_parts);
+			cstate->partition_dispatch_info = pd;
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
+														sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	partrel;
+
+				/*
+				 * All partitions locked above; will be closed after CopyFrom is
+				 * finished.
+				 */
+				partrel = heap_open(lfirst_oid(cell), NoLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(partrel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  partrel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(partrel),
+									gettext_noop("could not convert row type"));
+				leaf_part_rri++;
+				i++;
+			}
+		}
 	}
 	else
 	{
@@ -2255,6 +2320,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2347,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2458,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2486,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_dispatch_info != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2442,7 +2511,11 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2567,56 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_dispatch_info)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+											cstate->partition_dispatch_info,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/* We do not yet have a way to insert into a foreign partition */
+			if (resultRelInfo->ri_FdwRoutine)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot route inserted tuples to a foreign table")));
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2553,7 +2676,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2701,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2720,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2745,20 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/* Close all partitions and indices thereof */
+	if (cstate->partition_dispatch_info)
+	{
+		int		i;
+
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3b72ae3..6478211 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c7a6347..54fb771 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2990,3 +2995,52 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..aa1d8b9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_dispatch_info)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+										mtstate->mt_partition_dispatch_info,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* We do not yet have a way to insert into a foreign partition */
+		if (resultRelInfo->ri_FdwRoutine)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot route inserted tuples to a foreign table")));
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1713,69 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDispatch  *pd;
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+											  &leaf_parts);
+		mtstate->mt_partition_dispatch_info = pd;
+		num_leaf_parts = list_length(leaf_parts);
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2093,15 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index fb43cc1..1d231a3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -50,4 +52,8 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 								 List **leaf_part_oids);
+extern int get_partition_for_tuple(PartitionDispatch *pd,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..b4d09f9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionDispatch *pd,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..606cb21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,13 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionDispatchData **mt_partition_dispatch_info;
+										/* Tuple-routing support info */
+	int				mt_num_partitions;	/* Number of members in the
+										 * following arrays */
+	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
+	TupleConversionMap **mt_partition_tupconv_maps;
+									/* Per partition tuple conversion map */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-16.patchtext/x-diff; name=0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-16.patchDownload
From 51eac7fd13ab961ac95eaf9169cd73a9385cf59d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#137Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#135)
Re: Declarative partitioning - another take

On Thu, Nov 17, 2016 at 8:18 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The reason NULLs in an input row are caught and rejected (with the current
message) before control reaches range_partition_for_tuple() is because
it's not clear to me whether the range bound comparison logic in
partition_rbound_datum_cmp() should be prepared to handle NULLs and what
the results of comparisons should look like. Currently, all it ever
expects to see in the input tuple's partition key is non-NULL datums.
Comparison proceeds as follows: if a range bound datum is a finite value,
we invoke the comparison proc or if it is infinite, we conclude that the
input tuple is > or < the bound in question based on whether the bound is
a lower or upper bound, respectively.

Or are you saying that get_tuple_for_partition() should simply return -1
(partition not found) in case of encountering a NULL in range partition
key to the caller instead of throwing error as is now? If the user sees
the message and decides to create a new range partition that *will* accept
such a row, how do they decide what its boundaries should be?

Well, I think the first thing you have to decide is whether range
partitioning is going to support NULL values in the partition key
columns at all. If you want it to support that, then you've got to
decide how it's going to be specified in the SQL syntax. I had the
impression that you were planning not to support that, in which case
you need to reject all such rows.

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

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

#138Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#136)
Re: Declarative partitioning - another take

On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message about
NULLs in the range partition key)

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that
are partitioned be K. Assign each partitioned table in the hierarchy
an index between 0 and K-1. Make your top level data structure (in
lieu of PartitionTreeNodeData) be an array of K PartitionDispatch
objects, with the partitioning root in entry 0 and the rest in the
remaining entries.

2. Within each PartitionDispatch object, store (a) a pointer to a
PartitionDesc and (b) an array of integers of length equal to the
PartitionDesc's nparts value. Each integer i, if non-negative, is the
final return value for get_partition_for_tuple. If i == -1, tuple
routing fails. If i < -1, we must next route using the subpartition
whose PartitionDesc is at index -(i+1). Arrange for the array to be
in the same order the PartitionDesc's OID list.

3. Now get_partition_for_tuple looks something like this:

K = 0
loop:
pd = PartitionDispatch[K]
idx = list/range_partition_for_tuple(pd->partdesc, ...)
if (idx >= -1)
return idx
K = -(idx + 1)

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:

- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.

- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.

- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.

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

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

#139Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#138)
7 attachment(s)
Re: Declarative partitioning - another take

Updated patches attached. I merged what used to be 0006 and 0007 into one.

On 2016/11/19 2:23, Robert Haas wrote:

On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message about
NULLs in the range partition key)

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that

[ ... ]

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:

Some changes described below have reduced the size to a certain degree.

- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.

OK, I merged the fields of what used to be PartitionKeyExecInfo into
PartitionDispatchData as the latter's new fields key and keystate.

- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.

I have considered these suggestions in the latest patch. Now instead of
PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
there is only one PartitionBoundInfo which consolidates the partition
bound information of a partitioned table. Some of the fields are
applicable only to one of list or range case; for example, null-accepting
list partition index, infinite status of individual range datums.

Also, there is now only one binary search function named
partition_bound_bsearch() which invokes a comparison function named
partition_bound_cmp(). The former searches a probe (a partition bound or
tuple) within a PartitionBoundInfo, which is passed all the way down to
the comparison function.

Also, we no longer have list_partition_for_tuple() and
range_partition_for_tuple(). Instead, in get_partition_for_tuple()
itself, there is a bsearch followed by list and range partitioning
specific steps based on the returned offset.

- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.

OK, I've managed to get rid of lower. At least it is no longer kept in
the new relcache struct PartitionBoundInfo. It is still kept in
PartitionRangeBound which is used to hold individual range bounds when
sorting them (during relcache build). Comparisons invoked during the
aforementioned sorting step still need to distinguish between lower and
upper bounds (such that '1)' < '[1').

Tuple-routing no longer needs to look at lower. In that case, what you
described above applies.

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool. Upon encountering an infinite range bound datum, whether it's
negative or positive infinity derives the comparison result. Consider the
following example:

partition p1 from (1, unbounded) to (1, 1);
partition p2 from (1, 1) to (1, 10);
partition p3 from (1, 10) to (1, unbounded);
partition p4 from (2, unbounded) to (2, 1);
... so on

In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
(1, +inf), so that tuple (1, 15) is assigned to the proper partition.

Does this last thing sound reasonable?

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-17.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-17.patchDownload
From 9aaf1a63ec8fdb1ae6b6ecfe96f8356e2d69a13a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/7] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 +++++++-
 doc/src/sgml/ref/create_table.sgml         |   57 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  166 ++++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    2 +
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    1 +
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  450 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   14 +-
 src/backend/commands/vacuum.c              |    1 +
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    1 +
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 ++++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   68 +++++
 src/backend/rewrite/rewriteDefine.c        |    1 +
 src/backend/rewrite/rewriteHandler.c       |    1 +
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  268 ++++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   69 +++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 +++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   43 +++
 src/test/regress/expected/create_table.out |  158 ++++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   29 ++
 src/test/regress/sql/create_table.sql      |  143 +++++++++
 52 files changed, 1952 insertions(+), 56 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 561e228..26e5ce8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..1a95219 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,41 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1406,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 			case ACL_OBJECT_RELATION:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
 	{
 		/* Use binary-upgrade override for pg_class.oid/relfilenode? */
 		if (IsBinaryUpgrade &&
-			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			(relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+			 relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+			 relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+			 relkind == RELKIND_FOREIGN_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
 		switch (relkind)
 		{
 			case RELKIND_RELATION:
+			case RELKIND_PARTITIONED_TABLE:
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
 	 * such is an implementation detail: toast tables, sequences and indexes.
 	 */
 	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+							  relkind == RELKIND_PARTITIONED_TABLE ||
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			childrel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 			 * are the only relkinds for which pg_dump will dump labels).
 			 */
 			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 7e37108..e4d5350 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a table"),
 	gettext_noop("Use DROP TABLE to remove a table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{RELKIND_SEQUENCE,
 		ERRCODE_UNDEFINED_TABLE,
 		gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 {
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
-	char		relkind;
+	char		relkind,
+				expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 	 * restriction.
 	 */
 	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			rel->rd_rel->relkind == RELKIND_MATVIEW)
 		{
 			if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5842,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 	switch (tuple_class->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		rel->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 */
 	if (IsA(stmt, AlterObjectSchemaStmt) &&
 		relkind != RELKIND_RELATION &&
+		relkind != RELKIND_PARTITIONED_TABLE &&
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+						 pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+						 pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+			  errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
 	rel = heap_open(relid, AccessExclusiveLock);
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 * relation.
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
 		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
+						relkind == RELKIND_PARTITIONED_TABLE ||
 						relkind == RELKIND_MATVIEW)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	relation = relation_openrv(table_like_clause->relation, AccessShareLock);
 
 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
 	 * blocks them for users.  Don't mention them in the error message.
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		event_relation->rd_rel->relkind != RELKIND_VIEW)
 		ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
 	{
 		/*
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	Relation		catalog;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+	attrs = form->partattrs.values;
+
+	/*
+	 * To retrieve further variable-length attributes, we'd need the catalog's
+	 * tuple descriptor
+	 */
+	catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+	/* Operator class */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+						RelationGetDescr(catalog),
+						&isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = heap_getattr(tuple,
+						 Anum_pg_partitioned_table_partexprs,
+						 RelationGetDescr(catalog),
+						 &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		tuple;
+		Form_pg_opclass form;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		form = (Form_pg_opclass) GETSTRUCT(tuple);
+		key->partopfamily[i] = form->opcfamily;
+		key->partopcintype[i] = form->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(form->opcfamily,
+								   form->opcintype, form->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(tuple);
+	}
+
+	ReleaseSysCache(tuple);
+	heap_close(catalog, AccessShareLock);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_PARTITIONED_TABLE ||
+		 relkind == RELKIND_MATVIEW))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
 
 
 #define		  RELKIND_RELATION		  'r'		/* ordinary table */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
 #define		  RELKIND_SEQUENCE		  'S'		/* sequence object */
 #define		  RELKIND_TOASTVALUE	  't'		/* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of columns in the partition key */
+
+	/* variable-length fields start here, but we allow direct access to partattrs */
+	int2vector		partattrs;		/* attribute numbers of columns in the
+									 * partition key */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-17.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-17.patchDownload
From 2c25451cf621d77c7bed314021974c7fb8d016a7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/7] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   66 +++++++++++-
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 301 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9f59f53..d32d0ff 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1228,9 +1228,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2087,6 +2088,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4961,7 +4965,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4975,7 +4979,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5503,7 +5508,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6902,6 +6909,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14311,6 +14359,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14371,6 +14422,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -14389,7 +14441,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14606,6 +14659,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f3e5977..e9849ef 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -312,6 +312,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce..96e77ec 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1979,6 +1979,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+ d      | text    |           |          | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-17.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-17.patchDownload
From 5fd0ab9415aca0c11a1d64395f80908ef326c48a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/7] Catalog and DDL for partitions.

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

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

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

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

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

0004-psql-and-pg_dump-support-for-partitions-17.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-17.patchDownload
From ce1ce2e3f079c20a2825ec76680392797d4f1ea7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/7] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d32d0ff..467c243 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5610,9 +5610,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5639,6 +5646,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14217,6 +14288,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14245,8 +14327,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14275,7 +14360,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14333,15 +14419,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14511,6 +14604,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e9849ef..a7cb00a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -322,6 +322,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index bbb7e28..b5f6d06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-17.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-17.patchDownload
From ba96a90039e2ab752baa6488006580bac8c0b611 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/7] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   69 ++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  271 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   82 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   48 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 602 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..c7a6347 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1746,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1779,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1803,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30_inc
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_inc_ab
+   ->  Seq Scan on part_21_30_inc_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30_inc
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_inc_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 10).
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 49730ea..14ef189 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -200,3 +200,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail (a is null but a range partition key column should not be null)
+insert into part_b_10_b_20(b) values (10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index e0cf5d1..a7193c0 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -105,3 +105,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Tuple-routing-for-partitioned-tables-17.patchtext/x-diff; name=0006-Tuple-routing-for-partitioned-tables-17.patchDownload
From 5d560dfd04d7ab0009c0a9465e0f679c3a17a118 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/7] Tuple routing for partitioned tables.

Both COPY FROM and INSERT are covered by this commit.  Routing to foreing
partitions is not supported at the moment.

To implement tuple-routing, introduce a PartitionDispatch data structure.
Each partitioned table in a partition tree gets one and contains info
such as a pointer to its partition descriptor, partition key execution
state, global sequence numbers of its leaf partitions and a way to link
to the PartitionDispatch objects of any of its partitions that are
partitioned themselves. Starting with the PartitionDispatch object of the
root partitioned table and a tuple to route, one can get the global
sequence number of the leaf partition that the tuple gets routed to,
if one exists.
---
 src/backend/catalog/partition.c        |  342 ++++++++++++++++++++++++++++++++
 src/backend/commands/copy.c            |  151 ++++++++++++++-
 src/backend/commands/tablecmds.c       |    1 +
 src/backend/executor/execMain.c        |   58 ++++++-
 src/backend/executor/nodeModifyTable.c |  130 ++++++++++++
 src/backend/parser/analyze.c           |    8 +
 src/include/catalog/partition.h        |   11 +
 src/include/executor/executor.h        |    6 +
 src/include/nodes/execnodes.h          |    8 +
 src/test/regress/expected/insert.out   |   52 +++++
 src/test/regress/sql/insert.sql        |   25 +++
 11 files changed, 787 insertions(+), 5 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ba22453..643a258 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -109,6 +109,28 @@ typedef struct PartitionRangeBound
 	bool	lower;		/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+/*-----------------------
+ * PartitionDispatch - information about one partitioned table in a partition
+ * hiearchy required to route a tuple to one of its partitions
+ *
+ *	relid		OID of the table
+ *	key			Partition key information of the table
+ *	keystate	Execution state required for expressions in the partition key
+ *	partdesc	Partition descriptor of the table
+ *	indexes		Array with partdesc->nparts members (for details on what
+ *				individual members represent, see how they are set in
+ *				RelationGetPartitionDispatchInfo())
+ *-----------------------
+ */
+typedef struct PartitionDispatchData
+{
+	Oid						relid;
+	PartitionKey			key;
+	List				   *keystate;	/* list of ExprState */
+	PartitionDesc			partdesc;
+	int					   *indexes;
+} PartitionDispatchData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -122,12 +144,21 @@ static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, Li
 static int32 partition_rbound_cmp(PartitionKey key,
 					 Datum *datums1, bool *inf1, bool lower1,
 					 PartitionRangeBound *b2);
+static int32 partition_rbound_datum_cmp(PartitionKey key, Datum *rb_datums, bool *rb_inf,
+						   Datum *tuple_datums);
 
 static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 					int offset, void *probe, bool probe_is_bound);
 static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+/* Support get_partition_for_tuple() */
+static void FormPartitionKeyDatum(PartitionDispatch pd,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -888,6 +919,115 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/* Turn an array of OIDs with N elements into a list */
+#define OID_ARRAY_TO_LIST(arr, N, list) \
+	do\
+	{\
+		int		i;\
+		for (i = 0; i < (N); i++)\
+			(list) = lappend_oid((list), (arr)[i]);\
+	} while(0)
+
+/*
+ * RelationGetPartitionDispatchInfo
+ *		Returns information necessary to route tuples down a partition tree
+ *
+ * All the partitions will be locked with lockmode, unless it is NoLock.
+ * A list of the OIDs of all the leaf partition of rel is returned in
+ * *leaf_part_oids.
+ */
+PartitionDispatch *
+RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids)
+{
+	PartitionDesc	rootpartdesc = RelationGetPartitionDesc(rel);
+	PartitionDispatchData **pd;
+	List	   *all_parts = NIL,
+			   *parted_rels = NIL;
+	ListCell   *lc;
+	int			i,
+				k,
+				num_parted;
+
+	/*
+	 * Lock partitions and collect OIDs of the partitioned ones to prepare
+	 * their PartitionDispatch objects.
+	 *
+	 * Cannot use find_all_inheritors() here, because then the order of OIDs
+	 * in parted_rels list would be unknown, which does not help because down
+	 * below, we assign indexes within individual PartitionDispatch in an
+	 * order that's predetermined (determined by the order of OIDs in
+	 * individual partition descriptors).
+	 */
+	parted_rels = lappend_oid(parted_rels, RelationGetRelid(rel));
+	num_parted = 1;
+	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
+	foreach(lc, all_parts)
+	{
+		Relation		partrel = heap_open(lfirst_oid(lc), lockmode);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+
+		/*
+		 * If this partition is a partitined table, add its children to to the
+		 * end of the list, so that they are processed as well.
+		 */
+		if (partdesc)
+		{
+			num_parted++;
+			parted_rels = lappend_oid(parted_rels, lfirst_oid(lc));
+			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+		}
+
+		heap_close(partrel, NoLock);
+	}
+
+	/* Generate PartitionDispatch objects for all partitioned tables */
+	pd = (PartitionDispatchData **) palloc(num_parted *
+										sizeof(PartitionDispatchData *));
+	*leaf_part_oids = NIL;
+	i = k = 0;
+	foreach(lc, parted_rels)
+	{
+		/* We locked all partitions above */
+		Relation	partrel = heap_open(lfirst_oid(lc), NoLock);
+		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
+		int			j,
+					m;
+
+		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+		pd[i]->relid = RelationGetRelid(partrel);
+		pd[i]->key = RelationGetPartitionKey(partrel);
+		pd[i]->keystate = NIL;
+		pd[i]->partdesc = partdesc;
+		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
+		heap_close(partrel, NoLock);
+
+		m = 0;
+		for (j = 0; j < partdesc->nparts; j++)
+		{
+			Oid		partrelid = partdesc->oids[j];
+
+			if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
+			{
+				*leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
+				pd[i]->indexes[j] = k++;
+			}
+			else
+			{
+				/*
+				 * We can assign indexes this way because of the way
+				 * parted_rels has been generated.
+				 */
+				pd[i]->indexes[j] = -(i + 1 + m);
+				m++;
+			}
+		}
+		i++;
+	}
+
+	return pd;
+}
+
 /* Module-local functions */
 
 /*
@@ -1328,6 +1468,172 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionDispatch pd,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pd->key->partexprs != NIL && pd->keystate == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
+												estate);
+	}
+
+	partexpr_item = list_head(pd->keystate);
+	for (i = 0; i < pd->key->partnatts; i++)
+	{
+		AttrNumber	keycol = pd->key->partattrs[i];
+		Datum		datum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			datum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			datum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = datum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Finds a leaf partition for tuple contained in *slot
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionDispatch *pd,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionDispatch parent;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		cur_offset,
+			cur_index;
+	int		i;
+
+	/* start with the root partitioned table */
+	parent = pd[0];
+	while(true)
+	{
+		PartitionKey	key = parent->key;
+		PartitionDesc	partdesc = parent->partdesc;
+
+		/* Quick exit */
+		if (partdesc->nparts == 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+
+		/* Extract partition key from tuple */
+		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
+
+		if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			/* Disallow nulls in the range partition key of the tuple */
+			for (i = 0; i < key->partnatts; i++)
+				if (isnull[i])
+					ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key of row contains null")));
+		}
+
+		if (partdesc->boundinfo->has_null && isnull[0])
+			/* Tuple maps to the null-accepting list partition */
+			cur_index = partdesc->boundinfo->null_index;
+		else
+		{
+			/* Else bsearch in partdesc->boundinfo */
+			bool	equal = false;
+
+			cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
+												 values, false, &equal);
+			switch (key->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+					if (cur_offset >= 0 && equal)
+						cur_index = partdesc->boundinfo->indexes[cur_offset];
+					else
+						cur_index = -1;
+					break;
+
+				case PARTITION_STRATEGY_RANGE:
+					/*
+					 * Offset returned is such that the bound at offset is
+					 * found to be less or equal with the tuple. So, the
+					 * bound at offset+1 would be the upper bound.
+					 */
+					cur_index = partdesc->boundinfo->indexes[cur_offset+1];
+					break;
+			}
+		}
+
+		/*
+		 * cur_index < 0 means we failed to find a partition of this parent.
+		 * cur_index >= 0 means we either found the leaf partition, or the
+		 * next parent to find a partition of.
+		 */
+		if (cur_index < 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+		else if (parent->indexes[cur_index] < 0)
+			parent = pd[-parent->indexes[cur_index]];
+		else
+			break;
+	}
+
+	return parent->indexes[cur_index];
+}
+
 /* List partition related support functions */
 
 /*
@@ -1461,6 +1767,35 @@ partition_rbound_cmp(PartitionKey key,
 }
 
 /*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in datums, finite, and lower) is
+ * <=, =, >= partition key of tuple (tuple_datums)
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, Datum *rb_datums, bool *rb_inf,
+						   Datum *tuple_datums)
+{
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (rb_inf[i])
+			return rb_inf[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
  * partition_bound_cmp
  * 
  * Return whether the bound at offset in boundinfo is <=, =, >= the argument
@@ -1502,6 +1837,13 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 
 				cmpval = partition_rbound_cmp(key, datums1, inf1, lower1, b2);
 			}
+			else
+			{
+				Datum *datums2 = (Datum *) probe;
+
+				cmpval = partition_rbound_datum_cmp(key, datums1, inf1,
+													datums2);
+			}
 		}
 		break;
 	}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..7d76ead 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionDispatch	   *partition_dispatch_info;
+	int						num_partitions;
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1401,67 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDispatch *pd;
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i,
+							num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+
+			/* Get the tuple-routing information and lock partitions */
+			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+												  &leaf_parts);
+			num_leaf_parts = list_length(leaf_parts);
+			cstate->partition_dispatch_info = pd;
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
+														sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	partrel;
+
+				/*
+				 * All partitions locked above; will be closed after CopyFrom is
+				 * finished.
+				 */
+				partrel = heap_open(lfirst_oid(cell), NoLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(partrel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  partrel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(partrel),
+									gettext_noop("could not convert row type"));
+				leaf_part_rri++;
+				i++;
+			}
+		}
 	}
 	else
 	{
@@ -2255,6 +2320,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2347,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2458,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2486,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_dispatch_info != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2442,7 +2511,11 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2567,56 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_dispatch_info)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+											cstate->partition_dispatch_info,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/* We do not yet have a way to insert into a foreign partition */
+			if (resultRelInfo->ri_FdwRoutine)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot route inserted tuples to a foreign table")));
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2553,7 +2676,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2701,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2720,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2745,20 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/* Close all partitions and indices thereof */
+	if (cstate->partition_dispatch_info)
+	{
+		int		i;
+
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b27da1d..be08eff 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c7a6347..54fb771 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2990,3 +2995,52 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a612b08..aa1d8b9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_dispatch_info)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+										mtstate->mt_partition_dispatch_info,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* We do not yet have a way to insert into a foreign partition */
+		if (resultRelInfo->ri_FdwRoutine)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot route inserted tuples to a foreign table")));
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1713,69 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDispatch  *pd;
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+											  &leaf_parts);
+		mtstate->mt_partition_dispatch_info = pd;
+		num_leaf_parts = list_length(leaf_parts);
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2093,15 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 70d8325..f76c5d9 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -36,6 +38,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionDispatchData *PartitionDispatch;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +48,12 @@ extern void check_new_partition_bound(char *relname, Relation parent, Node *boun
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids);
+extern int get_partition_for_tuple(PartitionDispatch *pd,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..b4d09f9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionDispatch *pd,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..606cb21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,13 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionDispatchData **mt_partition_dispatch_info;
+										/* Tuple-routing support info */
+	int				mt_num_partitions;	/* Number of members in the
+										 * following arrays */
+	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
+	TupleConversionMap **mt_partition_tupconv_maps;
+									/* Per partition tuple conversion map */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-17.patchtext/x-diff; name=0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-17.patchDownload
From 174e6a6eb44ff52fae217793c2cb9ec4038ddc61 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 7/7] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#140Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Amit Langote (#139)
Re: Declarative partitioning - another take

Hi Amit,

I was just reading through your patches and here are some quick review
comments
for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch.

Review comments for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch:

1)
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
     {
         /* Use binary-upgrade override for pg_class.oid/relfilenode? */
         if (IsBinaryUpgrade &&
-            (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-             relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-             relkind == RELKIND_COMPOSITE_TYPE || relkind ==
RELKIND_FOREIGN_TABLE))
+            (relkind == RELKIND_RELATION || relkind ==
RELKIND_PARTITIONED_TABLE ||
+             relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+             relkind == RELKIND_MATVIEW || relkind ==
RELKIND_COMPOSITE_TYPE ||
+             relkind == RELKIND_FOREIGN_TABLE))

You should add the RELKIND_PARTITIONED_TABLE at the end of if condition that
will make diff minimal. While reading through the patch I noticed that there
is inconsistency - someplace its been added at the end and at few places its
at the start. I think you can make add it at the end of condition and be
consistent with each place.

2)

+        /*
+         * We need to transform the raw parsetrees corresponding to
partition
+         * expressions into executable expression trees.  Like column
defaults
+         * and CHECK constraints, we could not have done the transformation
+         * earlier.
+         */

Additional space before "Like column defaults".

3)
-    char        relkind;
+    char        relkind,
+                expected_relkind;

Newly added variable should be define separately with its type. Something
like:

char relkind;
+ char expected_relkind;

4)

a)
+    /* Prevent partitioned tables from becoming inheritance parents */
+    if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+        ereport(ERROR,
+                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                 errmsg("cannot inherit from partitioned table \"%s\"",
+                         parent->relname)));
+

need alignment for last line.

b)
+            atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
pelem->name);
+            if (!HeapTupleIsValid(atttuple))
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("column \"%s\" named in partition key does
not exist",
+                         pelem->name)));
+            attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+            if (attform->attnum <= 0)
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("cannot use system column \"%s\" in
partition key",
+                         pelem->name)));

need alignment for last line of ereport

c)
+        /* Disallow ROW triggers on partitioned tables */
+        if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("\"%s\" is a partitioned table",
+                            RelationGetRelationName(rel)),
+              errdetail("Partitioned tables cannot have ROW triggers.")));

need alignment

5)

@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt
*stmt,
     cxt.blist = NIL;
     cxt.alist = NIL;
     cxt.pkey = NULL;
+    cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;

I think adding bracket will look code more clear.

+ cxt.ispartitioned = (rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE);

6)

+ * RelationBuildPartitionKey
+ *        Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it
survives
+ * as long as the relcache.  To avoid leaking memory in that context in
case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.

extra space before "To avoid leaking memory"

7)
+    /* variable-length fields start here, but we allow direct access to
partattrs */
+    int2vector        partattrs;        /* attribute numbers of columns in
the

Why partattrs is allow direct access - its not really clear from the
comments.

I will continue reading more patch and testing functionality.. will share
the
comments as I have it.

Thanks,

On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Updated patches attached. I merged what used to be 0006 and 0007 into one.

On 2016/11/19 2:23, Robert Haas wrote:

On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message

about

NULLs in the range partition key)

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that

[ ... ]

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:

Some changes described below have reduced the size to a certain degree.

- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.

OK, I merged the fields of what used to be PartitionKeyExecInfo into
PartitionDispatchData as the latter's new fields key and keystate.

- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.

I have considered these suggestions in the latest patch. Now instead of
PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
there is only one PartitionBoundInfo which consolidates the partition
bound information of a partitioned table. Some of the fields are
applicable only to one of list or range case; for example, null-accepting
list partition index, infinite status of individual range datums.

Also, there is now only one binary search function named
partition_bound_bsearch() which invokes a comparison function named
partition_bound_cmp(). The former searches a probe (a partition bound or
tuple) within a PartitionBoundInfo, which is passed all the way down to
the comparison function.

Also, we no longer have list_partition_for_tuple() and
range_partition_for_tuple(). Instead, in get_partition_for_tuple()
itself, there is a bsearch followed by list and range partitioning
specific steps based on the returned offset.

- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.

OK, I've managed to get rid of lower. At least it is no longer kept in
the new relcache struct PartitionBoundInfo. It is still kept in
PartitionRangeBound which is used to hold individual range bounds when
sorting them (during relcache build). Comparisons invoked during the
aforementioned sorting step still need to distinguish between lower and
upper bounds (such that '1)' < '[1').

Tuple-routing no longer needs to look at lower. In that case, what you
described above applies.

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool. Upon encountering an infinite range bound datum, whether it's
negative or positive infinity derives the comparison result. Consider the
following example:

partition p1 from (1, unbounded) to (1, 1);
partition p2 from (1, 1) to (1, 10);
partition p3 from (1, 10) to (1, unbounded);
partition p4 from (2, unbounded) to (2, 1);
... so on

In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
(1, +inf), so that tuple (1, 15) is assigned to the proper partition.

Does this last thing sound reasonable?

Thanks,
Amit

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

--
Rushabh Lathia

#141Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#139)
Re: Declarative partitioning - another take

On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Why?

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool.

You better not be using a bool to represent a ternary value! Use an
enum for that -- or if in the system catalogs, a char.

Upon encountering an infinite range bound datum, whether it's
negative or positive infinity derives the comparison result. Consider the
following example:

partition p1 from (1, unbounded) to (1, 1);
partition p2 from (1, 1) to (1, 10);
partition p3 from (1, 10) to (1, unbounded);
partition p4 from (2, unbounded) to (2, 1);
... so on

In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
(1, +inf), so that tuple (1, 15) is assigned to the proper partition.

Right.

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

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

#142Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#139)
Re: Declarative partitioning - another take

I am trying to create a partitioned table with primary keys on the
partitions. Here's the corresponding syntax as per documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

The same syntax also suggests using table_constraints but that too doesn't work
create table t1_p1 partition of t1 (primary key (a) ) for values
from (0) to (100);
ERROR: inherited relation "t1" is not a table or foreign table

of course t1 is a table, what it isn't?

Am I missing something? How do I define constraints on the partitions?

On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Updated patches attached. I merged what used to be 0006 and 0007 into one.

On 2016/11/19 2:23, Robert Haas wrote:

On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message about
NULLs in the range partition key)

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that

[ ... ]

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:

Some changes described below have reduced the size to a certain degree.

- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.

OK, I merged the fields of what used to be PartitionKeyExecInfo into
PartitionDispatchData as the latter's new fields key and keystate.

- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.

I have considered these suggestions in the latest patch. Now instead of
PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
there is only one PartitionBoundInfo which consolidates the partition
bound information of a partitioned table. Some of the fields are
applicable only to one of list or range case; for example, null-accepting
list partition index, infinite status of individual range datums.

Also, there is now only one binary search function named
partition_bound_bsearch() which invokes a comparison function named
partition_bound_cmp(). The former searches a probe (a partition bound or
tuple) within a PartitionBoundInfo, which is passed all the way down to
the comparison function.

Also, we no longer have list_partition_for_tuple() and
range_partition_for_tuple(). Instead, in get_partition_for_tuple()
itself, there is a bsearch followed by list and range partitioning
specific steps based on the returned offset.

- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.

OK, I've managed to get rid of lower. At least it is no longer kept in
the new relcache struct PartitionBoundInfo. It is still kept in
PartitionRangeBound which is used to hold individual range bounds when
sorting them (during relcache build). Comparisons invoked during the
aforementioned sorting step still need to distinguish between lower and
upper bounds (such that '1)' < '[1').

Tuple-routing no longer needs to look at lower. In that case, what you
described above applies.

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool. Upon encountering an infinite range bound datum, whether it's
negative or positive infinity derives the comparison result. Consider the
following example:

partition p1 from (1, unbounded) to (1, 1);
partition p2 from (1, 1) to (1, 10);
partition p3 from (1, 10) to (1, unbounded);
partition p4 from (2, unbounded) to (2, 1);
... so on

In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
(1, +inf), so that tuple (1, 15) is assigned to the proper partition.

Does this last thing sound reasonable?

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#143Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Ashutosh Bapat (#142)
Re: Declarative partitioning - another take

Unsurprisingly ALTER TABLE worked
alter table t1_p1 add primary key(a);
ALTER TABLE
postgres=# \d+ t1_p1
Table "public.t1_p1"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | not null | | plain | |
b | integer | | | | plain | |
Indexes:
"t1_p1_pkey" PRIMARY KEY, btree (a)
Inherits: t1

On Thu, Nov 24, 2016 at 11:05 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

I am trying to create a partitioned table with primary keys on the
partitions. Here's the corresponding syntax as per documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

The same syntax also suggests using table_constraints but that too doesn't work
create table t1_p1 partition of t1 (primary key (a) ) for values
from (0) to (100);
ERROR: inherited relation "t1" is not a table or foreign table

of course t1 is a table, what it isn't?

Am I missing something? How do I define constraints on the partitions?

On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Updated patches attached. I merged what used to be 0006 and 0007 into one.

On 2016/11/19 2:23, Robert Haas wrote:

On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oh but wait, that means I can insert rows with NULLs in the range
partition key if I choose to insert it directly into the partition,
whereas I have been thinking all this while that there could never be
NULLs in the partition key of a range partition. What's more,
get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
every partition key column in case of a range partition. Is that
wrongheaded altogether? (also see my reply to your earlier message about
NULLs in the range partition key)

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Thanks for the idea below!

1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that

[ ... ]

No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.

This sounds *much* better. Here is a quick attempt at coding the design
you have outlined above in the attached latest set of patches.

That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:

Some changes described below have reduced the size to a certain degree.

- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.

OK, I merged the fields of what used to be PartitionKeyExecInfo into
PartitionDispatchData as the latter's new fields key and keystate.

- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.

I have considered these suggestions in the latest patch. Now instead of
PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
there is only one PartitionBoundInfo which consolidates the partition
bound information of a partitioned table. Some of the fields are
applicable only to one of list or range case; for example, null-accepting
list partition index, infinite status of individual range datums.

Also, there is now only one binary search function named
partition_bound_bsearch() which invokes a comparison function named
partition_bound_cmp(). The former searches a probe (a partition bound or
tuple) within a PartitionBoundInfo, which is passed all the way down to
the comparison function.

Also, we no longer have list_partition_for_tuple() and
range_partition_for_tuple(). Instead, in get_partition_for_tuple()
itself, there is a bsearch followed by list and range partitioning
specific steps based on the returned offset.

- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.

OK, I've managed to get rid of lower. At least it is no longer kept in
the new relcache struct PartitionBoundInfo. It is still kept in
PartitionRangeBound which is used to hold individual range bounds when
sorting them (during relcache build). Comparisons invoked during the
aforementioned sorting step still need to distinguish between lower and
upper bounds (such that '1)' < '[1').

Tuple-routing no longer needs to look at lower. In that case, what you
described above applies.

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool. Upon encountering an infinite range bound datum, whether it's
negative or positive infinity derives the comparison result. Consider the
following example:

partition p1 from (1, unbounded) to (1, 1);
partition p2 from (1, 1) to (1, 10);
partition p3 from (1, 10) to (1, unbounded);
partition p4 from (2, unbounded) to (2, 1);
... so on

In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
(1, +inf), so that tuple (1, 15) is assigned to the proper partition.

Does this last thing sound reasonable?

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#144Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#142)
Re: Declarative partitioning - another take

Hi Ashutosh,

On 2016/11/24 14:35, Ashutosh Bapat wrote:

I am trying to create a partitioned table with primary keys on the
partitions. Here's the corresponding syntax as per documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

The same syntax also suggests using table_constraints but that too doesn't work
create table t1_p1 partition of t1 (primary key (a) ) for values
from (0) to (100);
ERROR: inherited relation "t1" is not a table or foreign table

of course t1 is a table, what it isn't?

It's a bug. Forgot to consider RELKIND_PARTITIONED_TABLE to an if block
in the code that checks inheritance parent relation's relkind when
creating an index constraint (primary key) on a child table. Will fix,
thanks for catching it.

Thanks,
Amit

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

#145Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#144)
Re: Declarative partitioning - another take

On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Ashutosh,

On 2016/11/24 14:35, Ashutosh Bapat wrote:

I am trying to create a partitioned table with primary keys on the
partitions. Here's the corresponding syntax as per documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.

The same syntax also suggests using table_constraints but that too doesn't work
create table t1_p1 partition of t1 (primary key (a) ) for values
from (0) to (100);
ERROR: inherited relation "t1" is not a table or foreign table

of course t1 is a table, what it isn't?

It's a bug. Forgot to consider RELKIND_PARTITIONED_TABLE to an if block
in the code that checks inheritance parent relation's relkind when
creating an index constraint (primary key) on a child table. Will fix,
thanks for catching it.

Thanks,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#146Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#145)
Re: Declarative partitioning - another take

On 2016/11/24 15:10, Ashutosh Bapat wrote:

On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:

On 2016/11/24 14:35, Ashutosh Bapat wrote:

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.

I just adopted the existing syntax for specifying column/table constraints
of a table created with CREATE TABLE OF type_name.

Thanks,
Amit

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

#147Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#146)
Re: Declarative partitioning - another take

On Thu, Nov 24, 2016 at 12:02 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/24 15:10, Ashutosh Bapat wrote:

On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:

On 2016/11/24 14:35, Ashutosh Bapat wrote:

IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.

I just adopted the existing syntax for specifying column/table constraints
of a table created with CREATE TABLE OF type_name.

Hmm, I don't fine it quite intuitive. But others might find it so.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#148Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rushabh Lathia (#140)
7 attachment(s)
Re: Declarative partitioning - another take

Hi Rushabh,

On 2016/11/22 22:11, Rushabh Lathia wrote:

Hi Amit,

I was just reading through your patches and here are some quick review
comments
for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch.

Thanks for the review!

Review comments for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch:

1)
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
-            (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-             relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-             relkind == RELKIND_COMPOSITE_TYPE || relkind ==
RELKIND_FOREIGN_TABLE))
+            (relkind == RELKIND_RELATION || relkind ==
RELKIND_PARTITIONED_TABLE ||
+             relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+             relkind == RELKIND_MATVIEW || relkind ==
RELKIND_COMPOSITE_TYPE ||
+             relkind == RELKIND_FOREIGN_TABLE))

You should add the RELKIND_PARTITIONED_TABLE at the end of if condition that
will make diff minimal. While reading through the patch I noticed that there
is inconsistency - someplace its been added at the end and at few places its
at the start. I think you can make add it at the end of condition and be
consistent with each place.

OK, done.

2)

+        /*
+         * We need to transform the raw parsetrees corresponding to
partition
+         * expressions into executable expression trees.  Like column
defaults
+         * and CHECK constraints, we could not have done the transformation
+         * earlier.
+         */

Additional space before "Like column defaults".

I think it's a common practice to add two spaces after a sentence-ending
period [https://www.gnu.org/prep/standards/html_node/Comments.html], which
it seems, is followed more or less regularly in the formatting of the
comments in the PostgreSQL source code.

3)
-    char        relkind;
+    char        relkind,
+                expected_relkind;

Newly added variable should be define separately with its type. Something
like:

char relkind;
+ char expected_relkind;

OK, done.

4)

a)
+    /* Prevent partitioned tables from becoming inheritance parents */
+    if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+        ereport(ERROR,
+                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                 errmsg("cannot inherit from partitioned table \"%s\"",
+                         parent->relname)));
+

need alignment for last line.

Fixed.

b)
+            atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
pelem->name);
+            if (!HeapTupleIsValid(atttuple))
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("column \"%s\" named in partition key does
not exist",
+                         pelem->name)));
+            attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+            if (attform->attnum <= 0)
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("cannot use system column \"%s\" in
partition key",
+                         pelem->name)));

need alignment for last line of ereport

Fixed.

c)
+        /* Disallow ROW triggers on partitioned tables */
+        if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("\"%s\" is a partitioned table",
+                            RelationGetRelationName(rel)),
+              errdetail("Partitioned tables cannot have ROW triggers.")));

need alignment

Fixed.

5)

@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt
*stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+    cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;

I think adding bracket will look code more clear.

+ cxt.ispartitioned = (rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE);

Agreed, done.

6)

+ * RelationBuildPartitionKey
+ *        Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it
survives
+ * as long as the relcache.  To avoid leaking memory in that context in
case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.

extra space before "To avoid leaking memory"

Same thing I said above.

7)
+    /* variable-length fields start here, but we allow direct access to
partattrs */
+    int2vector        partattrs;        /* attribute numbers of columns in
the

Why partattrs is allow direct access - its not really clear from the
comments.

I have copied the comment from another catalog header file (a number of
catalog headers have the same comment). I updated the new file's comment
to say a little bit more; I wonder if the comment should be updated in
other files as well? However, I noticed that there are explanatory notes
elsewhere (for example, around the code that reads such a field from the
catalog) about why the first variable-length field of a catalog's tuple
(especially of type int2vector or oidvector) are directly accessible via
their C struct offsets.

I will continue reading more patch and testing functionality.. will share
the
comments as I have it.

Updated patches attached. I have also considered Robert's comments [1]/messages/by-id/CA+TgmoZ-feQsxc7U_JerM_AFChp3Qf6btK708SAe7M8Vdv5=jA@mail.gmail.com as
follows and fixed a bug that Ashutosh reported [2]/messages/by-id/306c85e9-c702-3742-eeff-9b7a40498afc@lab.ntt.co.jp:

- Force partition key columns to be NOT NULL at the table creation time
if using range partitioning
- Use enum to represent the content of individual range datums (viz.
finite datum, -infinity, +infinity) in the relcache struct

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZ-feQsxc7U_JerM_AFChp3Qf6btK708SAe7M8Vdv5=jA@mail.gmail.com
/messages/by-id/CA+TgmoZ-feQsxc7U_JerM_AFChp3Qf6btK708SAe7M8Vdv5=jA@mail.gmail.com
[2]: /messages/by-id/306c85e9-c702-3742-eeff-9b7a40498afc@lab.ntt.co.jp
/messages/by-id/306c85e9-c702-3742-eeff-9b7a40498afc@lab.ntt.co.jp

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-18.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-18.patchDownload
From 340f0262c07ce16fce27ae2716715238314acdbd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/7] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 ++++++-
 doc/src/sgml/ref/create_table.sgml         |   62 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  165 +++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    6 +-
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    3 +-
 src/backend/commands/sequence.c            |    3 +-
 src/backend/commands/tablecmds.c           |  509 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   16 +-
 src/backend/commands/vacuum.c              |    3 +-
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    3 +-
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 +++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   73 ++++-
 src/backend/rewrite/rewriteDefine.c        |    3 +-
 src/backend/rewrite/rewriteHandler.c       |    3 +-
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  265 ++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   76 ++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 ++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  168 +++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   32 ++
 src/test/regress/sql/create_table.sql      |  146 ++++++++
 52 files changed, 2047 insertions(+), 70 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 561e228..26e5ce8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..eeb9d59 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+     <para>
+      When using range partitioning, a <literal>NOT NULL</literal> constraint
+      is added to each non-expression column in the partition key.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1411,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..3086021 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				break;
 			case ACL_OBJECT_SEQUENCE:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..d7ce4ca 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1104,7 +1106,8 @@ heap_create_with_catalog(const char *relname,
 		if (IsBinaryUpgrade &&
 			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
 			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE ||
+			 relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1138,6 +1141,7 @@ heap_create_with_catalog(const char *relname,
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
+			case RELKIND_PARTITIONED_TABLE:
 				relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
 											  relnamespace);
 				break;
@@ -1182,7 +1186,8 @@ heap_create_with_catalog(const char *relname,
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
-							  relkind == RELKIND_COMPOSITE_TYPE))
+							  relkind == RELKIND_COMPOSITE_TYPE ||
+							  relkind == RELKIND_PARTITIONED_TABLE))
 		new_array_oid = AssignTypeArrayOid();
 
 	/*
@@ -1354,7 +1359,9 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE ||
+			   relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1808,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2046,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3042,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..f4afcd9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
-		onerel->rd_rel->relkind == RELKIND_MATVIEW)
+		onerel->rd_rel->relkind == RELKIND_MATVIEW ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
 		acquirefunc = acquire_sample_rows;
@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
-			childrel->rd_rel->relkind == RELKIND_MATVIEW)
+			childrel->rd_rel->relkind == RELKIND_MATVIEW ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			/* Regular table, so use the regular row acquisition function */
 			acquirefunc = acquire_sample_rows;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..2b0ae34 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 7e37108..1ab9030 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
-			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("referenced relation \"%s\" is not a table or foreign table",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..7e2beff 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -252,6 +253,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("foreign table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a foreign table"),
 	gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,65 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts,
+						i;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+		List		   *cmds = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+
+		/* Force key columns to be NOT NULL when using range partitioning */
+		if (strategy == PARTITION_STRATEGY_RANGE)
+		{
+			for (i = 0; i < partnatts; i++)
+			{
+				AttrNumber	partattno = partattrs[i];
+				Form_pg_attribute attform = descriptor->attrs[partattno-1];
+
+				if (partattno != 0 && !attform->attnotnull)
+				{
+					/* Add a subcommand to make this one NOT NULL */
+					AlterTableCmd *cmd = makeNode(AlterTableCmd);
+
+					cmd->subtype = AT_SetNotNull;
+					cmd->name = pstrdup(NameStr(attform->attname));
+					cmds = lappend(cmds, cmd);
+				}
+			}
+
+			/*
+			 * Although, there cannot be any partitions yet, we still need to
+			 * pass true for recurse; ATPrepSetNotNull() complains if we don't
+			 */
+			if (cmds != NIL)
+				AlterTableInternal(RelationGetRelid(rel), cmds, true);
+		}
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -927,6 +1006,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
 	char		relkind;
+	char		expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1035,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1385,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1614,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2166,7 +2266,8 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
 		relkind != RELKIND_INDEX &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
@@ -4291,6 +4392,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,7 +4629,8 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
-			rel->rd_rel->relkind == RELKIND_MATVIEW)
+			rel->rd_rel->relkind == RELKIND_MATVIEW ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			if (origTypeName)
 				ereport(ERROR,
@@ -5250,6 +5353,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If the table is a range partitioned table, check that the column
+	 * is not in the partition key.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionKey	key = RelationGetPartitionKey(rel);
+		int				partnatts = get_partition_natts(key),
+						i;
+
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber	partattnum = get_partition_col_attnum(key, i);
+
+			if (partattnum == attnum)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("column \"%s\" is in range partition key",
+								colName)));
+		}
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5419,7 +5544,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
@@ -5692,6 +5818,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5893,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5938,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6469,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8094,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7916,6 +8125,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7931,7 +8153,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8961,6 +9184,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_PARTITIONED_TABLE:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -9422,6 +9646,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_VIEW:
@@ -9842,7 +10067,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10267,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10092,6 +10323,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11481,7 +11719,8 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
-		rel->rd_rel->relkind == RELKIND_MATVIEW)
+		rel->rd_rel->relkind == RELKIND_MATVIEW ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
 		AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
@@ -11930,7 +12169,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12087,7 +12326,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
@@ -12095,3 +12335,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+								pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+								pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..02e9693 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+					 errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..b1be2f7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
-		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
+		onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		ereport(WARNING,
 				(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..29d5f57 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,7 +1886,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
-						relkind == RELKIND_MATVIEW)
+						relkind == RELKIND_MATVIEW ||
+						relkind == RELKIND_PARTITIONED_TABLE)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 367bc2e..1680fea 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -544,6 +546,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2811,69 +2817,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3418,6 +3430,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 17d1cbf..8a2bdf0 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..fc896a2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -763,7 +829,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
@@ -1854,7 +1921,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				rel = heap_openrv(inh, AccessShareLock);
 				/* check user requested inheritance from valid relkind */
 				if (rel->rd_rel->relkind != RELKIND_RELATION &&
-					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+					rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..32e1328 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename,
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
-		event_relation->rd_rel->relkind != RELKIND_VIEW)
+		event_relation->rd_rel->relkind != RELKIND_VIEW &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or view",
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..bf4f098 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
-		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
+		target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Emit CTID so that executor can find the row to update or delete.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..7f3ba74 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -435,6 +439,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
 			return;
@@ -796,6 +801,236 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/*
+	 * We can rely on the first variable-length attribute being mapped to
+	 * the relevant field of the catalog's C struct, because all previous
+	 * attributes are non-nullable and fixed-length.
+	 */
+	attrs = form->partattrs.values;
+
+	/* But use the hard way to retrieve further variable-length attributes */
+	/* Operator class */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partexprs, &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		opclasstup;
+		Form_pg_opclass opclassform;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		opclasstup = SearchSysCache1(CLAOID,
+									 ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(opclasstup))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
+		key->partopfamily[i] = opclassform->opcfamily;
+		key->partopcintype[i] = opclassform->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(opclassform->opcfamily,
+								   opclassform->opcintype,
+								   opclassform->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(opclasstup);
+	}
+
+	ReleaseSysCache(tuple);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1285,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2286,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3229,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_MATVIEW ||
+		 relkind == RELKIND_PARTITIONED_TABLE))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3762,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4526,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5296,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..6a86c93 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -161,6 +161,7 @@ DESCR("");
 #define		  RELKIND_COMPOSITE_TYPE  'c'		/* composite type */
 #define		  RELKIND_FOREIGN_TABLE   'f'		/* foreign table */
 #define		  RELKIND_MATVIEW		  'm'		/* materialized view */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 
 #define		  RELPERSISTENCE_PERMANENT	'p'		/* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u'		/* unlogged permanent table */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..cec54ae
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/*
+	 * variable-length fields start here, but we allow direct access to
+	 * partattrs via the C struct.  That's because the first variable-length
+	 * field of a heap tuple can be reliably accessed using its C struct
+	 * offset, as previous fields are all non-nullable fixed-length fields.
+	 */
+	int2vector		partattrs;		/* each member of the array is the
+									 * attribute number of a partition key
+									 * column, or 0 if the column is actually
+									 * an expression */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..df6fe13 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,49 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+ERROR:  column "a" is in range partition key
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..410d96b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,171 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+ attname | attnotnull 
+---------+------------
+ a       | t
+ b       | f
+ c       | t
+ d       | t
+(4 rows)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..ec61b02 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,35 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..b9489fc 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,149 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-18.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-18.patchDownload
From 7064b4be9b098a1172167db51bc5d87e98bdcffd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/7] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   68 +++++++++++--
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 302 insertions(+), 27 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..60fe794 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1415,6 +1417,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9f59f53..6d5a9ed 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1228,9 +1228,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2087,6 +2088,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4961,7 +4965,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4975,7 +4979,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5503,7 +5508,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6902,6 +6909,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14311,6 +14359,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14371,7 +14422,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
+			 tbinfo->relkind == RELKIND_FOREIGN_TABLE ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
 			{
@@ -14389,7 +14441,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14606,6 +14659,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f3e5977..e9849ef 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -312,6 +312,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..bc44ac5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -865,6 +865,7 @@ permissionsList(const char *pattern)
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
 					  gettext_noop("Schema"),
@@ -874,6 +875,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "c.relacl");
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2999,6 +3028,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 's' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3010,6 +3040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("sequence"),
 					  gettext_noop("special"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce..96e77ec 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1979,6 +1979,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 410d96b..02e0720 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -417,7 +417,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+ c      | text    |           | not null | 
+ d      | text    |           | not null | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index b9489fc..2af3214 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -411,7 +411,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-18.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-18.patchDownload
From 5c14a07462bd71a6dd4c9777ce15df96204105ab Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/7] Catalog and DDL for partitions.

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

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

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

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

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 26e5ce8..7b7b5e8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..5949837 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       values or to reject null values.  You can only use <literal>SET
       NOT NULL</> when the column contains no null values.
      </para>
+
+     <para>
+      If this table is a partition, one cannot perform <literal>DROP NOT NULL</>
+      on a column if it is marked <literal>NOT NULL</literal> in the parent
+      table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +712,52 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as a partition of the target table.  The partition bound specification
+      must correspond to the partitioning strategy and partition key of the
+      target table.  The table to be attached must have all the same columns
+      as the target table and no more; moreover, the column types must also
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints present in the target table.
+      If some <literal>CHECK</literal> constraint of the table being attached
+      is marked <literal>NO INHERIT</literal>, the command will fail; such
+      constraints must be recreated without the <literal>NO INHERIT</literal>
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is
+      possible to avoid this potentially expensive scan by adding a valid
+      <literal>CHECK</literal> constraint to the table that only allows rows
+      satisfying the desired partition constraint before trying to attach.
+      It will be determined using such a constraint that existing rows in the
+      table satisfy the partition constraint, so the table scan to check the
+      same will be skipped.  When adding a range partition or a list partition
+      that does not accept <literal>NULL</literal> values, also add
+      <literal>NOT NULL</literal> constraint to the partition key columns,
+      otherwise, the scan will be performed regardless of the existing
+      constraints.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +775,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +992,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1050,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition it is scanned to verify that
+    existing rows meet the partition constraint.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1124,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1313,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
     ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
 </programlisting></para>
 
+  <para>
+   Attach a partition to range partitioned table:
+<programlisting>
+ALTER TABLE measurement
+    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Detach a partition from partitioned table:
+<programlisting>
+ALTER TABLE cities
+    DETACH PARTITION measurement_y2015m12;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033..5d0dcf5 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
+    SERVER server_07;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index eeb9d59..27a391e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +247,50 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partition key of the parent table, and must not overlap with
+      any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1429,7 +1488,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d7ce4ca..9729bfa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1766,6 +1774,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1773,6 +1783,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1863,6 +1888,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2469,8 +2500,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2515,10 +2549,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3177,3 +3225,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..197fcef
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1550 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Information about bounds of a partitioned relation
+ *
+ * A list partition datum that is known to be NULL is never put into the
+ * datums array, instead it is tracked using has_null and null_index fields.
+ *
+ * In case of range partitioning, ndatums is far less than 2 * nparts, because
+ * a partition's upper bound and the next partition's lower bound are same
+ * in most common cases, and we only store one of them.
+ *
+ * In case of list partitioning, the indexes array stores one entry for every
+ * datum, which is the index of the partition that accepts a given datum.
+ * Wheareas, in case of range partitioning, it stores one entry per distinct
+ * range datum, which is the index of the partition of which a given datum
+ * is an upper bound.
+ */
+
+/* Ternary value to represent what's contained in a range bound datum */
+typedef enum RangeDatumContent
+{
+	RANGE_DATUM_FINITE = 0,		/* actual datum stored elsewhere */
+	RANGE_DATUM_NEG_INF,		/* negative infinity */
+	RANGE_DATUM_POS_INF			/* positive infinity */
+} RangeDatumContent;
+
+typedef struct PartitionBoundInfoData
+{
+	char		strategy;		/* list or range bounds? */
+	int			ndatums;		/* Length of the datums following array */
+	Datum	  **datums;			/* Array of datum-tuples with key->partnatts
+								 * datums each */
+	RangeDatumContent **content;	/* what's contained in each range bound
+									 * datum? (see the above enum); NULL for
+									 * list partitioned tables */
+	int		   *indexes;		/* Partition indexes; one entry per member of
+								 * the datums array (plus one if range
+								 * partitioned table) */
+	bool		has_null;		/* Is there a null-accepting partition? false
+								 * for range partitioned tables */
+	int			null_index;		/* Index of the null-accepting partition; -1
+								 * for range partitioned tables */
+} PartitionBoundInfoData;
+
+/*
+ * When qsort'ing partition bounds after reading from the catalog, each bound
+ * is represented with one of the following structs.
+ */
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	int		index;
+	Datum	value;
+} PartitionListValue;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;		/* range bound datums */
+	RangeDatumContent *content;	/* what's contained in each datum?*/
+	bool	lower;		/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static int32 partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2);
+
+static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound);
+static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	int		ndatums;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	bool	found_null = false;
+	int		null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null = false;
+				null_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					if (spec->strategy != PARTITION_STRATEGY_LIST)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null)
+								elog(ERROR, "found null more than once");
+							found_null = true;
+							null_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				ndatums = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **) palloc(ndatums *
+											sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp, (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+				bool   *distinct_indexes;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+				distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					if (spec->strategy != PARTITION_STRATEGY_RANGE)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					lower = make_one_range_bound(key, i, spec->lowerdatums,
+												 true);
+					upper = make_one_range_bound(key, i, spec->upperdatums,
+												 false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				ndatums = 0;
+				prev = NULL;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+					bool	is_distinct = false;
+					int		j;
+
+					/* Is current bound is distinct from the previous? */
+					for (j = 0; j < key->partnatts; j++)
+					{
+						Datum	cmpval;
+
+						if (prev == NULL)
+						{
+							is_distinct = true;
+							break;
+						}
+
+						/*
+						 * If either of them has infinite element, we can't
+						 * equate them.  Even when both are infinite, they'd
+						 * have opposite signs, because only one of cur and
+						 * prev is a lower bound).
+						 */
+						if (cur->content[j] != RANGE_DATUM_FINITE ||
+							prev->content[j] != RANGE_DATUM_FINITE)
+						{
+							is_distinct = true;
+							break;
+						}
+						cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+												   key->partcollation[j],
+												   cur->datums[j],
+												   prev->datums[j]);
+						if (DatumGetInt32(cmpval) != 0)
+						{
+							is_distinct = true;
+							break;
+						}
+					}
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.  Also, store if the index i contains
+					 * a distinct bound that we'd like put in the relcache
+					 * array.
+					 */
+					if (is_distinct)
+					{
+						distinct_indexes[i] = true;
+						ndatums++;
+					}
+					else
+						distinct_indexes[i] = false;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				rbounds = (PartitionRangeBound **) palloc(ndatums *
+											sizeof(PartitionRangeBound *));
+				k = 0;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					if (distinct_indexes[i])
+						rbounds[k++] = all_bounds[i];
+				}
+				Assert(k == ndatums);
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+									palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->ndatums = ndatums;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				boundinfo->has_null = found_null;
+				boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+				/*
+				 * Copy values.  Indexes of individual values are mapped to
+				 * canonical values so that they match for any two list
+				 * partitioned tables with same number of partitions and same
+				 * lists per partition.  One way to canonicalize is to assign
+				 * the index in all_values[] of the smallest value of each
+				 * partition, as the index of all of the partition's values.
+				 */
+				for (i = 0; i < ndatums; i++)
+				{
+					boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+					boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If the old index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					boundinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * If null-accepting partition has no mapped index yet, assign
+				 * one.  This could happen if such partition accepts only null
+				 * and hence not covered in the above loop which only handled
+				 * non-null values.
+				 */
+				if (found_null)
+				{
+					Assert(null_index >= 0);
+					if (mapping[null_index] == -1)
+						mapping[null_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null)
+					boundinfo->null_index = mapping[null_index];
+				else
+					boundinfo->null_index = -1;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				boundinfo->content = (RangeDatumContent **) palloc(ndatums *
+												 sizeof(RangeDatumContent *));
+				boundinfo->indexes = (int *) palloc((ndatums+1) *
+													sizeof(int));
+
+				for (i = 0; i < ndatums; i++)
+				{
+					int		j;
+
+					boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+															sizeof(Datum));
+					boundinfo->content[i] = (RangeDatumContent *)
+												palloc(key->partnatts *
+												   sizeof(RangeDatumContent));
+					for (j = 0; j < key->partnatts; j++)
+					{
+						if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
+							boundinfo->datums[i][j] =
+											datumCopy(rbounds[i]->datums[j],
+													  key->parttypbyval[j],
+													  key->parttyplen[j]);
+						/* Remember, we are storing the tri-state value. */
+						boundinfo->content[i][j] = rbounds[i]->content[j];
+					}
+
+					/*
+					 * There is no mapping for invalid indexes.
+					 *
+					 * Any lower bounds in the rbounds array have invalid
+					 * indexes assigned, because the values between the
+					 * previous bound (if there is one) and this (lower)
+					 * bound are not part of the range of any existing
+					 * partition.
+					 */
+					if (rbounds[i]->lower)
+						boundinfo->indexes[i] = -1;
+					else
+					{
+						int		orig_index = rbounds[i]->index;
+
+						/* If the old index is has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[orig_index];
+					}
+				}
+				boundinfo->indexes[i] = -1;
+				break;
+			}
+		}
+
+		result->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int		i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->has_null != b2->has_null)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	for (i = 0; i < b1->ndatums; i++)
+	{
+		int		j;
+
+		for (j = 0; j < key->partnatts; j++)
+		{
+			int32	cmpval;
+
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+													 key->partcollation[0],
+													 b1->datums[i][j],
+													 b2->datums[i][j]));
+			if (cmpval != 0)
+				return false;
+
+			/* Range partitions can have infinite datums */
+			if (b1->content != NULL && b1->content[i][j] != b2->content[i][j])
+				return false;
+		}
+
+		if(b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+
+	/* There are ndatums+1 indexes in case of range partitions */
+	if (key->strategy == PARTITION_STRATEGY_RANGE &&
+		b1->indexes[i] != b2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				ListCell   *cell;
+
+				Assert(boundinfo &&
+					   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
+					   (boundinfo->ndatums > 0 || boundinfo->has_null));
+
+				foreach (cell, spec->listdatums)
+				{
+					Const  *val = lfirst(cell);
+
+					if (!val->constisnull)
+					{
+						int		offset;
+						bool	equal;
+
+						offset = partition_bound_bsearch(key, boundinfo,
+														 &val->constvalue,
+														 true, &equal);
+						if (offset >= 0 && equal)
+						{
+							overlap = true;
+							with = boundinfo->indexes[offset];
+							break;
+						}
+					}
+					else if (boundinfo->has_null)
+					{
+						overlap = true;
+						with = boundinfo->null_index;
+						break;
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+			upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified lower and upper bounds
+			 */
+			if (partition_rbound_cmp(key, lower->datums, lower->content, true,
+									 upper) >= 0)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				int		  off1, off2;
+				bool	  equal = false;
+
+				Assert(boundinfo && boundinfo->ndatums > 0 &&
+					   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than or equal with the new lower bound.
+				 */
+				off1 = partition_bound_bsearch(key, boundinfo, lower, true,
+											   &equal);
+
+				/*
+				 * If equal has been set to true, that means the new lower
+				 * bound is found to be equal with the bound at off1, which
+				 * clearly means an overlap with the partition at index
+				 * off1+1).
+				 *
+				 * Otherwise, check if there is a "gap" that could be occupied
+				 * by the new partition.  In case of a gap, the new upper
+				 * bound should not cross past the upper boundary of the gap,
+				 * that is, off2 == off1 should be true.
+				 */
+				if (!equal && boundinfo->indexes[off1+1] < 0)
+				{
+					off2 = partition_bound_bsearch(key, boundinfo, upper,
+												   true, &equal);
+
+					if (equal || off1 != off2)
+					{
+						overlap = true;
+						with = boundinfo->indexes[off2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					with = boundinfo->indexes[off1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * We must remove any NULL value in the list; we handle it separately
+	 * below.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/*
+		 * Gin up a col IS NOT NULL test that will be AND'd with other
+		 * expressions
+		 */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/*
+		 * Gin up a col IS NULL test that will be OR'd with other expressions
+		 */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr containing this partition's values */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(key_col, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key, emitting an OpExpr for each using
+	 * the corresponding lower and upper datums as constant operands.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *key_col;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		Oid			operoid;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/*
+		 * Stop at this column if either of lower or upper datum is infinite,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (key_col = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create the test expression */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* This can never be, but it's better to make sure */
+				if (i == key->partnatts - 1)
+					elog(ERROR, "invalid range bound specification");
+
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					key_col = (Node *) makeRelabelType((Expr *) key_col,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) key_col,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val != upper_val.  Emit expressions
+		 * (key_col >= lower_val) and (key_col < upper_val), then stop.
+		 */
+		if (lower_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTGreaterEqualStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTLessStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * opreator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	if (!rel->rd_rel->relispartition)	/* should not happen */
+		elog(ERROR, "relation \"%s\" has relispartition = false",
+					RelationGetRelationName(rel));
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)		/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+					RelationGetRelationName(rel));
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			val1 = (*(const PartitionListValue **) a)->value,
+					val2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * make_one_range_bound
+ *
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.  Made into a function
+ * because there are multiple sites that want to use this facility.
+ */
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->content = (RangeDatumContent *) palloc0(key->partnatts *
+												sizeof(RangeDatumContent));
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		/* What's contained in this range datum? */
+		bound->content[i] = !datum->infinite
+								? RANGE_DATUM_FINITE
+								: (lower ? RANGE_DATUM_NEG_INF
+										 : RANGE_DATUM_POS_INF);
+
+		if (bound->content[i] == RANGE_DATUM_FINITE)
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
+}
+
+/*
+ * partition_rbound_cmp
+ * 
+ * Return for two range bounds whether the 1st one (specified in datum1,
+ * content1, and lower1) is <=, =, >= the bound specified in *b2
+ */
+static int32
+partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2)
+{
+	int32	cmpval;
+	int		i;
+	Datum  *datums2 = b2->datums;
+	RangeDatumContent *content2 = b2->content;
+	bool	lower2 = b2->lower;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (content1[i] != RANGE_DATUM_FINITE &&
+			content2[i] != RANGE_DATUM_FINITE)
+			/*
+			 * Both are infinity, so they are equal unless one is
+			 * negative infinity and other positive (or vice versa)
+			 */
+			return content1[i] == content2[i] ? 0
+								: (content1[i] < content2[i] ? -1 : 1);
+		else if (content1[i] != RANGE_DATUM_FINITE)
+			return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+		else if (content2[i] != RANGE_DATUM_FINITE)
+			return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i],
+												 datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If
+	 * they compare equal though, we still have to consider whether
+	 * the boundaries are inclusive or exclusive.  Exclusive one is
+	 * considered smaller of the two.
+	 */
+	if (cmpval == 0 && lower1 != lower2)
+		cmpval = lower1 ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
+ * partition_bound_cmp
+ * 
+ * Return whether the bound at offset in boundinfo is <=, =, >= the argument
+ * specified in *probe.
+ */
+static int32
+partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound)
+{
+	Datum  *bound_datums = boundinfo->datums[offset];
+	int32	cmpval;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   bound_datums[0],
+										   *(Datum *) probe));
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			RangeDatumContent  *content = boundinfo->content[offset];
+
+			if (probe_is_bound)
+			{
+				/*
+				 * We need to pass whether the existing bound is a lower
+				 * bound, so that two equal-valued lower and upper bounds are
+				 * not regarded equal.
+				 */
+				bool	lower = boundinfo->indexes[offset] < 0;
+
+				cmpval = partition_rbound_cmp(key,
+											  bound_datums, content, lower,
+											  (PartitionRangeBound *) probe);
+			}
+
+			break;
+		}
+	}
+
+	return cmpval;
+}
+
+/*
+ * Binary search on a collection of partition bounds. Returns greatest index
+ * of bound in array boundinfo->datums which is less or equal with *probe.
+ * If all bounds in the array are greater than *probe, -1 is returned.
+ *
+ * *probe could either be a partition bound or a Datum array representing
+ * the partition key of a tuple being routed; probe_is_bound tells which.
+ * We pass that down to the comparison function so that it can interpret the
+ * contents of *probe accordingly.
+ *
+ * *is_equal is set to whether the bound at the returned index is equal with
+ * *probe.
+ */
+static int
+partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal)
+{
+	int		lo,
+			hi,
+			mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_bound_cmp(key, boundinfo, mid, probe,
+									 probe_is_bound);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 1ab9030..d953b44 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7e2beff..99c9d10 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -65,6 +66,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
 	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;		/* if above is true */
+	List	   *partition_constraint; /* for attach partition validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
+							 stmt->partbound != NULL,
 							 &inheritOids, &old_constraints, &parentOidCount);
 
 	/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	descriptor = BuildDescForRelation(schema);
 
 	/*
-	 * Notice that we allow OIDs here only for plain tables, even though some
-	 * other relkinds can support them.  This is necessary because the
-	 * default_with_oids GUC must apply only to plain tables and not any other
-	 * relkind; doing otherwise would break existing pg_dump files.  We could
-	 * allow explicit "WITH OIDS" while not allowing default_with_oids to
-	 * affect other relkinds, but it would complicate interpretOidsOption().
+	 * Notice that we allow OIDs here only for plain tables and partitioned
+	 * tables, even though some other relkinds can support them.  This is
+	 * necessary because the default_with_oids GUC must apply only to plain
+	 * tables and not any other relkind; doing otherwise would break existing
+	 * pg_dump files.  We could allow explicit "WITH OIDS" while not allowing
+	 * default_with_oids to affect other relkinds, but it would complicate
+	 * interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
 									   (relkind == RELKIND_RELATION ||
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parent;
+
+		/* Already have strong enough lock on the parent */
+		parent = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partition key of parentRel, so it better have one.
+		 */
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parent))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parent, stmt->partbound);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parent, bound);
+		heap_close(parent, NoLock);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partition key information into the catalog.
@@ -1146,6 +1219,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1452,6 +1529,7 @@ storage_name(char c)
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of names (as RangeVar nodes) of parent relations.
  * 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
  *
  * Output arguments:
  * 'supOids' receives a list of the OIDs of the parent relations.
@@ -1503,7 +1581,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1513,6 +1592,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema = NIL;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1532,6 +1612,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 						MaxHeapAttributeNumber)));
 
 	/*
+	 * In case of a partition, there are no new column definitions, only
+	 * column options specified using the WITH OPTIONS clauses.  We merge
+	 * those options with actual column definitions after we have finished
+	 * generating them from the parent's schema.
+	 */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
+	/*
 	 * Check for duplicate names in the explicit list of attributes.
 	 *
 	 * Although we might consider merging such entries in the same way that we
@@ -1611,18 +1703,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * We do not allow partitioned tables and partitions to participate
+		 * in regular inheritance.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
 
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1632,7 +1741,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1640,7 +1751,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1887,7 +2000,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	/*
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.
+	 * columns into the inherited schema list.  Although, we never have any
+	 * explicitly declared columns if the table is a partition.
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1916,6 +2030,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
+				 * Partitions have only one parent, so conflict should never
+				 * occur
+				 */
+				Assert(!is_partition);
+
+				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
 				 */
@@ -1997,6 +2117,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have the column definition list for a partition, we can
+	 * check whether the columns referenced in column option specifications
+	 * actually exist.  Also, we merge the options into the corresponding
+	 * column definitions.
+	 */
+	if (is_partition && list_length(saved_schema) > 0)
+	{
+		schema = list_concat(schema, saved_schema);
+
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
+
+			/*
+			 * Partition column option that does not belong to a column from
+			 * the parent.  This works because the columns from the parent
+			 * come first in the list (see above).
+			 */
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" does not exist",
+							coldef->colname)));
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);		/* need to save it in case
+													 * we delete it */
+
+				if (strcmp(coldef->colname, restdef->colname) == 0)
+				{
+					/*
+					 * merge the column options into the column from the
+					 * parent
+					 */
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					list_delete_cell(schema, rest, prev);
+				}
+				prev = rest;
+				rest = next;
+			}
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -3158,6 +3328,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);
@@ -3269,12 +3444,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3475,6 +3652,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);
@@ -3545,7 +3728,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, we did
+		 * not modify anything about it that will change its toasting
+		 * requirement, so no need to check.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  tab->partition_constraint == NIL) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3794,6 +3984,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);
@@ -3979,7 +4175,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			if (tab->constraints != NIL || tab->new_notnull)
+			if (tab->constraints != NIL || tab->new_notnull ||
+				tab->partition_constraint != NIL)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4059,6 +4256,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4123,6 +4321,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_constraint)
+	{
+		needscan = true;
+		partqualstate = (List *)
+						ExecPrepareExpr((Expr *) tab->partition_constraint,
+										estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4312,6 +4519,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4509,7 +4721,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4831,6 +5044,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5277,6 +5495,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5352,6 +5584,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * If the table is a range partitioned table, check that the column
 	 * is not in the partition key.
@@ -5406,6 +5655,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5965,6 +6229,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7967,6 +8240,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10268,6 +10551,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10280,12 +10568,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;
 
@@ -10330,37 +10613,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from partitioned table \"%s\"",
 						parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10395,6 +10652,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);
 
@@ -10409,16 +10729,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;
 }
 
 /*
@@ -10469,7 +10781,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
@@ -10487,12 +10799,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		child_is_partition = false;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10540,6 +10857,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (child_is_partition)
+			{
+				Assert(childatt->attinhcount == 1);
+				childatt->attislocal = false;
+			}
+
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10562,7 +10891,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.
@@ -10581,10 +10910,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		child_is_partition = false;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10661,6 +10995,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (child_is_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10685,6 +11031,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10698,13 +11084,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];
@@ -10713,19 +11097,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
-
-	/*
-	 * 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);
+	bool		child_is_partition = false;
 
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10735,7 +11111,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);
 
@@ -10756,11 +11132,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	if (!found)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_TABLE),
-				 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-						RelationGetRelationName(parent_rel),
-						RelationGetRelationName(rel))));
+	{
+		if (child_is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+							RelationGetRelationName(child_rel),
+							RelationGetRelationName(parent_rel))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+							RelationGetRelationName(parent_rel),
+							RelationGetRelationName(child_rel))));
+	}
 
 	/*
 	 * Search through child columns looking for ones matching parent rel
@@ -10769,7 +11154,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)))
@@ -10831,7 +11216,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);
 
@@ -10862,7 +11247,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)
@@ -10874,30 +11259,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;
 }
 
 /*
@@ -12582,3 +12957,428 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	Relation	attachRel,
+				catalog;
+	List	   *childrels;
+	TupleConstr	*attachRel_constr;
+	List	   *partConstraint,
+			   *existConstraint;
+	SysScanDesc scan;
+	ScanKeyData skey;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	bool		skip_validate = false;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * attachRel should not already be part of inheritance; either as a child
+	 * table...
+	 */
+	catalog = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or be a RELKIND_RELATION parent table */
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+							  1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(catalog, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* If attachRel is temp, it must belong to this session */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach a temporary relation of another session as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel), rel,
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * Generate partition constraint from the partition bound specification.
+	 * If the parent itself is a partition, make sure to include its
+	 * constraint as well.
+	 */
+	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+														 cmd->bound),
+								 RelationGetPartitionQual(rel, true));
+	partConstraint = (List *) eval_const_expressions(NULL,
+													 (Node *) partConstraint);
+	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+	partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+	/*
+	 * Check if we can do away with having to scan the table being attached
+	 * to validate the partition constraint, by *proving* that the existing
+	 * constraints of the table *imply* the partition predicate.  We include
+	 * the table's check constraints and NOT NULL constraints in the list of
+	 * clauses passed to predicate_implied_by().
+	 *
+	 * There is a case in which we cannot rely on just the result of the
+	 * proof.
+	 */
+	tupleDesc = RelationGetDescr(attachRel);
+	attachRel_constr = tupleDesc->constr;
+	existConstraint = NIL;
+	if (attachRel_constr > 0)
+	{
+		int			num_check = attachRel_constr->num_check;
+		int			i;
+		Bitmapset  *not_null_attrs = NULL;
+
+		if (attachRel_constr->has_not_null)
+		{
+			int			natts = attachRel->rd_att->natts;
+
+			for (i = 1; i <= natts; i++)
+			{
+				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+				if (att->attnotnull && !att->attisdropped)
+				{
+					NullTest   *ntest = makeNode(NullTest);
+
+					ntest->arg = (Expr *) makeVar(1,
+												  i,
+												  att->atttypid,
+												  att->atttypmod,
+												  att->attcollation,
+												  0);
+					ntest->nulltesttype = IS_NOT_NULL;
+
+					/*
+					 * argisrow=false is correct even for a composite column,
+					 * because attnotnull does not represent a SQL-spec IS NOT
+					 * NULL test in such a case, just IS DISTINCT FROM NULL.
+					 */
+					ntest->argisrow = false;
+					ntest->location = -1;
+					existConstraint = lappend(existConstraint, ntest);
+					not_null_attrs = bms_add_member(not_null_attrs, i);
+				}
+			}
+		}
+
+		for (i = 0; i < num_check; i++)
+		{
+			Node	   *cexpr;
+
+			/*
+			 * If this constraint hasn't been fully validated yet, we must
+			 * ignore it here.
+			 */
+			if (!attachRel_constr->check[i].ccvalid)
+				continue;
+
+			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+			/*
+			 * Run each expression through const-simplification and
+			 * canonicalization.  It is necessary, because we will be
+			 * comparing it to similarly-processed qual clauses, and may fail
+			 * to detect valid matches without this.
+			 */
+			cexpr = eval_const_expressions(NULL, cexpr);
+			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+			existConstraint = list_concat(existConstraint,
+										  make_ands_implicit((Expr *) cexpr));
+		}
+
+		existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+		/* And away we go ... */
+		if (predicate_implied_by(partConstraint, existConstraint))
+			skip_validate = true;
+
+		/*
+		 * We choose to err on the safer side, ie, give up on skipping the
+		 * the validation scan, if the partition key column doesn't have
+		 * the NOT NULL constraint and the table is to become a list partition
+		 * that does not accept nulls.  In this case, the partition predicate
+		 * (partConstraint) does include an IS NOT NULL expression, however,
+		 * because of the way predicate_implied_by_simple_clause() is designed
+		 * to handle IS NOT NULL predicates in the absence of a IS NOT NULL
+		 * clause, we cannot rely on just the above proof.
+		 *
+		 * That is not an issue in case of a range partition, because if there
+		 * were no NOT NULL constraint defined on the key columns, an error
+		 * would be thrown before we get here anyway.
+		 */
+		if (key->strategy == PARTITION_STRATEGY_LIST)
+		{
+			List   *part_constr;
+			ListCell *lc;
+			bool	partition_accepts_null = true;
+
+			/*
+			 * Partition does not accept nulls if there is a IS NOT NULL
+			 * expression in the partition constraint.
+			 */
+			part_constr = linitial(partConstraint);
+			part_constr = make_ands_implicit((Expr *) part_constr);
+			foreach(lc, part_constr)
+			{
+				Node *expr = lfirst(lc);
+
+				if (IsA(expr, NullTest) &&
+					((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
+				{
+					partition_accepts_null = false;
+					break;
+				}
+			}
+
+			if (!partition_accepts_null &&
+				!bms_is_member(get_partition_col_attnum(key, 0),
+							   not_null_attrs))
+				skip_validate = false;
+		}
+	}
+
+	if (skip_validate)
+		elog(NOTICE, "skipping scan to validate partition constraint");
+
+	/*
+	 * Set up to have the table to be scanned to validate the partition
+	 * constraint (see partConstraint above).  If it's a partitioned table,
+	 * we instead schdule its leaf partitions to be scanned instead.
+	 */
+	if (!skip_validate)
+	{
+		List	   *all_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+											 AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, all_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+
+			/* Lock already taken */
+			if (part_relid != RelationGetRelid(attachRel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = attachRel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel != attachRel &&
+				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, part_rel);
+
+			constr = linitial(partConstraint);
+			tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+			/* keep our lock until commit */
+			if (part_rel != attachRel)
+				heap_close(part_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that the new partition is now included
+	 * in rel's partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_NODE_FIELD(lowerdatums);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1680fea..452d8a9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -550,6 +551,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -575,7 +583,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
@@ -591,7 +599,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
@@ -2377,6 +2386,31 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			| ATTACH PARTITION qualified_name ForValues
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
 		;
 
 alter_column_default:
@@ -2472,6 +2506,73 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerdatums = $5;
+					n->upperdatums = $9;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2889,6 +2990,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2934,6 +3073,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2956,6 +3100,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2967,6 +3122,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4554,6 +4714,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;
+				}
 		;
 
 /*****************************************************************************
@@ -13741,6 +13943,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13787,6 +13990,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index fc896a2..e64f4a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2581,6 +2586,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2663,6 +2669,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3027,3 +3046,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partition key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("FROM must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("TO must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+									*rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7f3ba74..c958092 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2);
 
 
 /*
@@ -1158,6 +1161,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2)
+{
+	int		i;
+
+	if (partdesc1 != NULL)
+	{
+		if (partdesc2 == NULL)
+			return false;
+		if (partdesc1->nparts != partdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || partdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < partdesc1->nparts; i++)
+		{
+			if (partdesc1->oids[i] != partdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is local to partition.c.
+		 */
+		if (partdesc1->boundinfo != NULL)
+		{
+			if (partdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, partdesc1->boundinfo,
+											 partdesc2->boundinfo))
+				return false;
+		}
+		else if (partdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (partdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1285,13 +1340,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2288,6 +2348,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2436,11 +2500,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2451,6 +2516,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);
@@ -2481,6 +2547,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
@@ -2536,6 +2605,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 */
@@ -3770,6 +3846,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5298,6 +5377,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..70d8325
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	PartitionBoundInfo	boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo p1, PartitionBoundInfo p2);
+
+extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 6a86c93..a61b7a2 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d30c82b..427eff2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/*
+	 * Range partition lower and upper bounds; each member of the lists
+	 * is a PartitionRangeDatum (see below).
+	 */
+	List	   *lowerdatums;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1577,7 +1622,9 @@ typedef enum AlterTableType
 	AT_DisableRowSecurity,		/* DISABLE ROW SECURITY */
 	AT_ForceRowSecurity,		/* FORCE ROW SECURITY */
 	AT_NoForceRowSecurity,		/* NO FORCE ROW SECURITY */
-	AT_GenericOptions			/* OPTIONS (...) */
+	AT_GenericOptions,			/* OPTIONS (...) */
+	AT_AttachPartition,			/* ATTACH PARTITION */
+	AT_DetachPartition			/* DETACH PARTITION */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de3..cd7ea1d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index df6fe13..1ed2558 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3020,3 +3020,294 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+--
+-- ATTACH PARTITION
+--
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+NOTICE:  skipping scan to validate partition constraint
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+NOTICE:  skipping scan to validate partition constraint
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+NOTICE:  skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  "part_2" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted2" is already a child of "list_parted2".
+--
+-- DETACH PARTITION
+--
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR:  relation "part_1" is not a partition of relation "list_parted2"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+DROP TABLE part_3_4;
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR:  cannot drop inherited constraint "check_a2" of relation "part_2"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR:  cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR:  cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR:  cannot change inheritance of a partition
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part_2
+drop cascades to table part_5
+drop cascades to table part_5_a
+drop cascades to table part_1
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 02e0720..783143d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -439,3 +439,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partition key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR:  TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR:  cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR:  partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ec61b02..901c8e7 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1907,3 +1907,259 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+--
+-- ATTACH PARTITION
+--
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+DROP TABLE part_3_4;
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ALTER TABLE part_2 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ALTER TABLE part_2 INHERIT inh_test;
+
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2af3214..8696a85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1

0004-psql-and-pg_dump-support-for-partitions-18.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-18.patchDownload
From 6a2311c304954f5f29974546c215d56dbd5d03cb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/7] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60fe794..4e2ba19 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8450,6 +8450,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6d5a9ed..5c85b8b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5610,9 +5610,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5639,6 +5646,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14217,6 +14288,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14245,8 +14327,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14275,7 +14360,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14333,15 +14419,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14511,6 +14604,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e9849ef..a7cb00a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -322,6 +322,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bc44ac5..def1d50 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 783143d..d504b05 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -601,6 +601,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 8696a85..ab77f99 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -565,6 +565,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-18.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-18.patchDownload
From 77d3fd0a72ac9f7f8f15b56124ad257412e1cac1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/7] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   69 ++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  272 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   78 +++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   52 ++++++
 src/test/regress/sql/insert.sql        |   56 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 600 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..c7a6347 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1746,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1779,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1803,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29d5f57..6eccfb7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index ad07baa..a2cbf14 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1139,6 +1140,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..38ea8e8 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,275 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_ab
+   ->  Seq Scan on part_21_30_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30
+drop cascades to table part_21_30_ab
+drop cascades to table part_21_30_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..cc9f957 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,81 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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 table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff_1_10
+drop cascades to table part_ee_ff_10_20
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 609899e..a1e9255 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -198,3 +198,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..e22a14e 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,55 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..3a9430e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+insert into part_a_1_a_10 values ('b', 1);
+-- ok
+insert into part_a_1_a_10 values ('a', 1);
+-- fail
+insert into part_b_10_b_20 values ('b', 21);
+insert into part_b_10_b_20 values ('a', 10);
+-- ok
+insert into part_b_10_b_20 values ('b', 10);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (upper(a));
+create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB');
+create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_AA_BB values ('cc', 1);
+insert into part_AA_BB values ('AAa', 1);
+-- ok
+insert into part_CC_DD values ('cC', 1);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b);
+create table part_EE_FF_1_10 partition of part_EE_FF for values from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (20);
+
+-- fail (both its own and all ancestors' partition bound spec applies)
+insert into part_EE_FF_1_10 values ('EE', 11);
+insert into part_EE_FF_1_10 values ('cc', 1);
+-- ok
+insert into part_EE_FF_1_10 values ('ff', 1);
+insert into part_EE_FF_10_20 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index ad58273..d7721ed 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -106,3 +106,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Tuple-routing-for-partitioned-tables-18.patchtext/x-diff; name=0006-Tuple-routing-for-partitioned-tables-18.patchDownload
From d7034ae8a510315af8ec5d077082861b18866c28 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/7] Tuple routing for partitioned tables.

Both COPY FROM and INSERT are covered by this commit.  Routing to foreing
partitions is not supported at the moment.

To implement tuple-routing, introduce a PartitionDispatch data structure.
Each partitioned table in a partition tree gets one and contains info
such as a pointer to its partition descriptor, partition key execution
state, global sequence numbers of its leaf partitions and a way to link
to the PartitionDispatch objects of any of its partitions that are
partitioned themselves. Starting with the PartitionDispatch object of the
root partitioned table and a tuple to route, one can get the global
sequence number of the leaf partition that the tuple gets routed to,
if one exists.
---
 src/backend/catalog/partition.c        |  342 +++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c            |  151 ++++++++++++++-
 src/backend/commands/tablecmds.c       |    1 +
 src/backend/executor/execMain.c        |   58 ++++++-
 src/backend/executor/nodeModifyTable.c |  130 ++++++++++++
 src/backend/parser/analyze.c           |    8 +
 src/include/catalog/partition.h        |   11 +
 src/include/executor/executor.h        |    6 +
 src/include/nodes/execnodes.h          |    8 +
 src/test/regress/expected/insert.out   |   52 +++++
 src/test/regress/sql/insert.sql        |   25 +++
 11 files changed, 786 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 197fcef..710829a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,28 @@ typedef struct PartitionRangeBound
 	bool	lower;		/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+/*-----------------------
+ * PartitionDispatch - information about one partitioned table in a partition
+ * hiearchy required to route a tuple to one of its partitions
+ *
+ *	relid		OID of the table
+ *	key			Partition key information of the table
+ *	keystate	Execution state required for expressions in the partition key
+ *	partdesc	Partition descriptor of the table
+ *	indexes		Array with partdesc->nparts members (for details on what
+ *				individual members represent, see how they are set in
+ *				RelationGetPartitionDispatchInfo())
+ *-----------------------
+ */
+typedef struct PartitionDispatchData
+{
+	Oid						relid;
+	PartitionKey			key;
+	List				   *keystate;	/* list of ExprState */
+	PartitionDesc			partdesc;
+	int					   *indexes;
+} PartitionDispatchData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -126,12 +148,22 @@ static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, Li
 static int32 partition_rbound_cmp(PartitionKey key,
 					 Datum *datums1, RangeDatumContent *content1, bool lower1,
 					 PartitionRangeBound *b2);
+static int32 partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums);
 
 static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 					int offset, void *probe, bool probe_is_bound);
 static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+/* Support get_partition_for_tuple() */
+static void FormPartitionKeyDatum(PartitionDispatch pd,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -895,6 +927,115 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/* Turn an array of OIDs with N elements into a list */
+#define OID_ARRAY_TO_LIST(arr, N, list) \
+	do\
+	{\
+		int		i;\
+		for (i = 0; i < (N); i++)\
+			(list) = lappend_oid((list), (arr)[i]);\
+	} while(0)
+
+/*
+ * RelationGetPartitionDispatchInfo
+ *		Returns information necessary to route tuples down a partition tree
+ *
+ * All the partitions will be locked with lockmode, unless it is NoLock.
+ * A list of the OIDs of all the leaf partition of rel is returned in
+ * *leaf_part_oids.
+ */
+PartitionDispatch *
+RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids)
+{
+	PartitionDesc	rootpartdesc = RelationGetPartitionDesc(rel);
+	PartitionDispatchData **pd;
+	List	   *all_parts = NIL,
+			   *parted_rels = NIL;
+	ListCell   *lc;
+	int			i,
+				k,
+				num_parted;
+
+	/*
+	 * Lock partitions and collect OIDs of the partitioned ones to prepare
+	 * their PartitionDispatch objects.
+	 *
+	 * Cannot use find_all_inheritors() here, because then the order of OIDs
+	 * in parted_rels list would be unknown, which does not help because down
+	 * below, we assign indexes within individual PartitionDispatch in an
+	 * order that's predetermined (determined by the order of OIDs in
+	 * individual partition descriptors).
+	 */
+	parted_rels = lappend_oid(parted_rels, RelationGetRelid(rel));
+	num_parted = 1;
+	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
+	foreach(lc, all_parts)
+	{
+		Relation		partrel = heap_open(lfirst_oid(lc), lockmode);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+
+		/*
+		 * If this partition is a partitined table, add its children to to the
+		 * end of the list, so that they are processed as well.
+		 */
+		if (partdesc)
+		{
+			num_parted++;
+			parted_rels = lappend_oid(parted_rels, lfirst_oid(lc));
+			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+		}
+
+		heap_close(partrel, NoLock);
+	}
+
+	/* Generate PartitionDispatch objects for all partitioned tables */
+	pd = (PartitionDispatchData **) palloc(num_parted *
+										sizeof(PartitionDispatchData *));
+	*leaf_part_oids = NIL;
+	i = k = 0;
+	foreach(lc, parted_rels)
+	{
+		/* We locked all partitions above */
+		Relation	partrel = heap_open(lfirst_oid(lc), NoLock);
+		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
+		int			j,
+					m;
+
+		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+		pd[i]->relid = RelationGetRelid(partrel);
+		pd[i]->key = RelationGetPartitionKey(partrel);
+		pd[i]->keystate = NIL;
+		pd[i]->partdesc = partdesc;
+		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
+		heap_close(partrel, NoLock);
+
+		m = 0;
+		for (j = 0; j < partdesc->nparts; j++)
+		{
+			Oid		partrelid = partdesc->oids[j];
+
+			if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
+			{
+				*leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
+				pd[i]->indexes[j] = k++;
+			}
+			else
+			{
+				/*
+				 * We can assign indexes this way because of the way
+				 * parted_rels has been generated.
+				 */
+				pd[i]->indexes[j] = -(i + 1 + m);
+				m++;
+			}
+		}
+		i++;
+	}
+
+	return pd;
+}
+
 /* Module-local functions */
 
 /*
@@ -1329,6 +1470,172 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionDispatch pd,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pd->key->partexprs != NIL && pd->keystate == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
+												estate);
+	}
+
+	partexpr_item = list_head(pd->keystate);
+	for (i = 0; i < pd->key->partnatts; i++)
+	{
+		AttrNumber	keycol = pd->key->partattrs[i];
+		Datum		datum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			datum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			datum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = datum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Finds a leaf partition for tuple contained in *slot
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionDispatch *pd,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionDispatch parent;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		cur_offset,
+			cur_index;
+	int		i;
+
+	/* start with the root partitioned table */
+	parent = pd[0];
+	while(true)
+	{
+		PartitionKey	key = parent->key;
+		PartitionDesc	partdesc = parent->partdesc;
+
+		/* Quick exit */
+		if (partdesc->nparts == 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+
+		/* Extract partition key from tuple */
+		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
+
+		if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			/* Disallow nulls in the range partition key of the tuple */
+			for (i = 0; i < key->partnatts; i++)
+				if (isnull[i])
+					ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key of row contains null")));
+		}
+
+		if (partdesc->boundinfo->has_null && isnull[0])
+			/* Tuple maps to the null-accepting list partition */
+			cur_index = partdesc->boundinfo->null_index;
+		else
+		{
+			/* Else bsearch in partdesc->boundinfo */
+			bool	equal = false;
+
+			cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
+												 values, false, &equal);
+			switch (key->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+					if (cur_offset >= 0 && equal)
+						cur_index = partdesc->boundinfo->indexes[cur_offset];
+					else
+						cur_index = -1;
+					break;
+
+				case PARTITION_STRATEGY_RANGE:
+					/*
+					 * Offset returned is such that the bound at offset is
+					 * found to be less or equal with the tuple. So, the
+					 * bound at offset+1 would be the upper bound.
+					 */
+					cur_index = partdesc->boundinfo->indexes[cur_offset+1];
+					break;
+			}
+		}
+
+		/*
+		 * cur_index < 0 means we failed to find a partition of this parent.
+		 * cur_index >= 0 means we either found the leaf partition, or the
+		 * next parent to find a partition of.
+		 */
+		if (cur_index < 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+		else if (parent->indexes[cur_index] < 0)
+			parent = pd[-parent->indexes[cur_index]];
+		else
+			break;
+	}
+
+	return parent->indexes[cur_index];
+}
+
 /*
  * qsort_partition_list_value_cmp
  *
@@ -1461,6 +1768,36 @@ partition_rbound_cmp(PartitionKey key,
 }
 
 /*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in rb_datums, rb_content, and
+ * rb_lower) <=, =, >= partition key of tuple (tuple_datums)
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums)
+{
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (rb_content[i] != RANGE_DATUM_FINITE)
+			return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
  * partition_bound_cmp
  * 
  * Return whether the bound at offset in boundinfo is <=, =, >= the argument
@@ -1499,7 +1836,10 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 											  bound_datums, content, lower,
 											  (PartitionRangeBound *) probe);
 			}
-
+			else
+				cmpval = partition_rbound_datum_cmp(key,
+													bound_datums, content,
+													(Datum *) probe);
 			break;
 		}
 	}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..7d76ead 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionDispatch	   *partition_dispatch_info;
+	int						num_partitions;
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1401,67 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDispatch *pd;
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i,
+							num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+
+			/* Get the tuple-routing information and lock partitions */
+			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+												  &leaf_parts);
+			num_leaf_parts = list_length(leaf_parts);
+			cstate->partition_dispatch_info = pd;
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
+														sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	partrel;
+
+				/*
+				 * All partitions locked above; will be closed after CopyFrom is
+				 * finished.
+				 */
+				partrel = heap_open(lfirst_oid(cell), NoLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(partrel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  partrel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(partrel),
+									gettext_noop("could not convert row type"));
+				leaf_part_rri++;
+				i++;
+			}
+		}
 	}
 	else
 	{
@@ -2255,6 +2320,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2347,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2458,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2486,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_dispatch_info != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2442,7 +2511,11 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2567,56 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_dispatch_info)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+											cstate->partition_dispatch_info,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/* We do not yet have a way to insert into a foreign partition */
+			if (resultRelInfo->ri_FdwRoutine)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot route inserted tuples to a foreign table")));
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+				tuple = do_convert_tuple(tuple, map);
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2553,7 +2676,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2701,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2720,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2745,20 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/* Close all partitions and indices thereof */
+	if (cstate->partition_dispatch_info)
+	{
+		int		i;
+
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 99c9d10..8a8eb01 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1322,6 +1322,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c7a6347..54fb771 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2990,3 +2995,52 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6eccfb7..87a04aa 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_dispatch_info)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+										mtstate->mt_partition_dispatch_info,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* We do not yet have a way to insert into a foreign partition */
+		if (resultRelInfo->ri_FdwRoutine)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot route inserted tuples to a foreign table")));
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1713,69 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDispatch  *pd;
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+											  &leaf_parts);
+		mtstate->mt_partition_dispatch_info = pd;
+		num_leaf_parts = list_length(leaf_parts);
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2093,15 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 36f8c54..88380ba 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 70d8325..f76c5d9 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -36,6 +38,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionDispatchData *PartitionDispatch;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +48,12 @@ extern void check_new_partition_bound(char *relname, Relation parent, Node *boun
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids);
+extern int get_partition_for_tuple(PartitionDispatch *pd,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..b4d09f9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionDispatch *pd,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..606cb21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,13 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionDispatchData **mt_partition_dispatch_info;
+										/* Tuple-routing support info */
+	int				mt_num_partitions;	/* Number of members in the
+										 * following arrays */
+	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
+	TupleConversionMap **mt_partition_tupconv_maps;
+									/* Per partition tuple conversion map */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index cc9f957..9706034 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -223,6 +223,58 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+    tableoid    | a | b  
+----------------+---+----
+ part_a_1_a_10  | a |  1
+ part_a_1_a_10  | a |  1
+ part_a_10_a_20 | a | 10
+ part_b_1_b_10  | b |  1
+ part_b_10_b_20 | b | 10
+ part_b_10_b_20 | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_EE_FF values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+     tableoid     | a  | b  
+------------------+----+----
+ part_aa_bb       | aA |   
+ part_cc_dd       | cC |  1
+ part_null        |    |  0
+ part_null        |    |  1
+ part_ee_ff_1_10  | ff |  1
+ part_ee_ff_1_10  | EE |  1
+ part_ee_ff_10_20 | ff | 11
+ part_ee_ff_10_20 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3a9430e..afecb74 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -137,6 +137,31 @@ insert into part_EE_FF_1_10 values ('cc', 1);
 insert into part_EE_FF_1_10 values ('ff', 1);
 insert into part_EE_FF_10_20 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_EE_FF not found)
+insert into list_parted values ('EE', 0);
+insert into part_EE_FF values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_EE_FF values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-18.patchtext/x-diff; name=0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-18.patchDownload
From fb91f1fc7e7b13aef79a2dce8332fc42c8a8e394 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 7/7] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#149Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#141)
Re: Declarative partitioning - another take

On 2016/11/23 4:50, Robert Haas wrote:

On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Why?

I was thinking of it like how primary key columns cannot contain
expressions; the reason for which, I assume, is because while we can
ensure that a column contains only non-null values by defining a
constraint on the column, there is no way to force expressions to be non-null.

In any case, I have implemented in the latest patch that when creating a
range partitioned table, its partition key columns are automatically set
to be NOT NULL. Although, it leaves out the columns that are referenced
in expressions. So even after doing so, we need to check after computing
the range partition key of a input row, that none of the partitions keys
is null, because expressions can still return null.

Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).

An alternative possibly worth considering might be to somehow handle the
null range partition keys within the logic to compare against range bound
datums. It looks like other databases will map the rows containing nulls
to the unbounded partition. One database allows specifying NULLS
FIRST/LAST and maps a row containing null key to the partition with
-infinity as the lower bound or +infinity as the upper bound, respectively
with NULLS LAST the default behavior.

In our case, if we allowed a similar way of defining a range partitioned
table:

create table p (a int, b int) partition by range nulls first (a);
create table p0 partition of p for values from (unbounded) to (1);
create table p1 partition of p for values from (1) to (10);
create table p2 partition of p for values from (10) to (unbounded);

Row (null, 1) will be mapped to p0. If we didn't have p0, we would report
the "partition not found" error.

In case of a multi-column key:

create table p (a int, b int) partition by range (a, b);
create table p0 partition of p for values from (1, unbounded) to (1, 1);
create table p1 partition of p for values from (1, 1) to (1, 10);
create table p2 partition of p for values from (1, 10) to (1, unbounded);

Row (1, null) will be mapped to p2 (default nulls last behavior).

But I guess we still end up without a solution for the problem that a row
with null partition key (expression) could be inserted into any of the
range partitions if targeted directly.

Thoughts?

As a result, one change became necessary: to how we flag individual range
bound datum as infinite or not. Previously, it was a regular Boolean
value (either infinite or not) and to distinguish +infinity from
-infinity, we looked at whether the bound is lower or upper (the lower
flag). Now, instead, the variable holding the status of individual range
bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
a bool.

You better not be using a bool to represent a ternary value! Use an
enum for that -- or if in the system catalogs, a char.

OK, created an enum called RangeDatumContent. In the system catalog, we
still store the boolean value; it is only after we read it into the
relcache structure that we use one of these enum values. I'm worried
though that using enum would consume more memory (we need to store nparts
* partnattrs instances of the enum).

Thanks,
Amit

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

#150Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Amit Langote (#146)
Re: Declarative partitioning - another take

Amit Langote wrote:

On 2016/11/24 15:10, Ashutosh Bapat wrote:

On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.

I just adopted the existing syntax for specifying column/table constraints
of a table created with CREATE TABLE OF type_name.

I think CREATE TABLE OF is pretty much a corner case. I agree that
allowing the constraint right after the constraint name is more
intuitive.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#151Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Alvaro Herrera (#150)
Re: Declarative partitioning - another take

On 2016/11/25 4:36, Alvaro Herrera wrote:

Amit Langote wrote:

On 2016/11/24 15:10, Ashutosh Bapat wrote:

On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:

You have to specify column constraints using the keywords WITH OPTIONS,
like below:

create table p1 partition of p (
a with options primary key
) for values in (1);

Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.

I just adopted the existing syntax for specifying column/table constraints
of a table created with CREATE TABLE OF type_name.

I think CREATE TABLE OF is pretty much a corner case. I agree that
allowing the constraint right after the constraint name is more
intuitive.

I assume you meant "...right after the column name"?

I will modify the grammar to allow that way then, so that the following
will work:

create table p1 partition of p (
a primary key
) for values in (1);

Thanks,
Amit

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

#152Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#149)
Re: Declarative partitioning - another take

On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/23 4:50, Robert Haas wrote:

On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.

Agreed that forcing range partitioning columns to be NOT NULL during table
creation would be a better approach. But then we would have to reject
using expressions in the range partition key, right?

Why?

I was thinking of it like how primary key columns cannot contain
expressions; the reason for which, I assume, is because while we can
ensure that a column contains only non-null values by defining a
constraint on the column, there is no way to force expressions to be non-null.

In any case, I have implemented in the latest patch that when creating a
range partitioned table, its partition key columns are automatically set
to be NOT NULL. Although, it leaves out the columns that are referenced
in expressions. So even after doing so, we need to check after computing
the range partition key of a input row, that none of the partitions keys
is null, because expressions can still return null.

Right. And ensuring that those columns were NOT NULL would be wrong,
as it wouldn't guarantee a non-null result anyway.

Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).

That's going to have to be fixed somehow. How bad would it be if we
passed ExecQual's third argument as false for partition constraints?
Or else you could generate the actual constraint as expr IS NOT NULL
AND expr >= lb AND expr < ub.

An alternative possibly worth considering might be to somehow handle the
null range partition keys within the logic to compare against range bound
datums. It looks like other databases will map the rows containing nulls
to the unbounded partition. One database allows specifying NULLS
FIRST/LAST and maps a row containing null key to the partition with
-infinity as the lower bound or +infinity as the upper bound, respectively
with NULLS LAST the default behavior.

It seems more future-proof not to allow NULLs at all for now, and
figure out what if anything we want to do about that later. I mean,
with the syntax we've got here, anything else is basically deciding
whether NULL is the lowest value or the highest value. It would be
convenient for my employer if we made the same decision that Oracle
did, here, but it doesn't really seem like the PostgreSQL way - or to
put that another way, it's really ugly and unprincipled. So I
recommend we decide for now that a partitioning column can't be null
and a partitioning expression can't evaluate to NULL. If it does,
ERROR.

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

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

#153Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Amit Langote (#151)
Re: Declarative partitioning - another take

Amit Langote wrote:

On 2016/11/25 4:36, Alvaro Herrera wrote:

I think CREATE TABLE OF is pretty much a corner case. I agree that
allowing the constraint right after the constraint name is more
intuitive.

I assume you meant "...right after the column name"?

Eh, right.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#154Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#151)
Re: Declarative partitioning - another take

I assume you meant "...right after the column name"?

I will modify the grammar to allow that way then, so that the following
will work:

create table p1 partition of p (
a primary key
) for values in (1);

That seems to be non-intuitive as well. The way it's written it looks
like "a primary key" is associated with p rather than p1.

Is there any column constraint that can not be a table constraint? If
no, then we can think of dropping column constraint syntax all
together and let the user specify column constraints through table
constraint syntax. OR we may drop constraints all-together from the
"CREATE TABLE .. PARTITION OF" syntax and let user handle it through
ALTER TABLE commands. In a later version, we will introduce constraint
syntax in that DDL.

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#155Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#154)
Re: Declarative partitioning - another take

On 2016/11/25 13:51, Ashutosh Bapat wrote:

I assume you meant "...right after the column name"?

I will modify the grammar to allow that way then, so that the following
will work:

create table p1 partition of p (
a primary key
) for values in (1);

That seems to be non-intuitive as well. The way it's written it looks
like "a primary key" is associated with p rather than p1.

It kind of does, but it's still a "create table p1" statement, so it's not
that ambiguous, IMHO. Although I'm not attached to this syntax, there may
not be other options, such as moving the parenthesized list right before
"partition of", due to resulting grammar conflicts.

Is there any column constraint that can not be a table constraint?

NOT NULL, DEFAULT, and COLLATE (although only the first of these is really
a constraint, albeit without a pg_constraint entry)

If no, then we can think of dropping column constraint syntax all
together and let the user specify column constraints through table
constraint syntax. OR we may drop constraints all-together from the
"CREATE TABLE .. PARTITION OF" syntax and let user handle it through
ALTER TABLE commands. In a later version, we will introduce constraint
syntax in that DDL.

Hmm, it was like that in the past versions of the patch, but I thought
it'd be better to follow the CREATE TABLE OF style to allow specifying the
table and column constraints during table creation time. If many think
that it is not required (or should have some other syntax), I will modify
the patch accordingly.

Thanks,
Amit

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

#156Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#152)
Re: Declarative partitioning - another take

On 2016/11/25 11:44, Robert Haas wrote:

On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:

Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).

That's going to have to be fixed somehow. How bad would it be if we
passed ExecQual's third argument as false for partition constraints?
Or else you could generate the actual constraint as expr IS NOT NULL
AND expr >= lb AND expr < ub.

About the former, I think that might work. If a column is NULL, it would
be caught in ExecConstraints() even before ExecQual() is called, because
of the NOT NULL constraint. If an expression is NULL, or for some reason,
the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
column or expression, then ExecQual() would fail if we passed false for
resultForNull. Not sure if that would be violating the SQL specification
though.

The latter would work too. But I guess we would only emit expr IS NOT
NULL, not column IS NOT NULL, because columns are covered by NOT NULL
constraints.

An alternative possibly worth considering might be to somehow handle the
null range partition keys within the logic to compare against range bound
datums. It looks like other databases will map the rows containing nulls
to the unbounded partition. One database allows specifying NULLS
FIRST/LAST and maps a row containing null key to the partition with
-infinity as the lower bound or +infinity as the upper bound, respectively
with NULLS LAST the default behavior.

It seems more future-proof not to allow NULLs at all for now, and
figure out what if anything we want to do about that later. I mean,
with the syntax we've got here, anything else is basically deciding
whether NULL is the lowest value or the highest value. It would be
convenient for my employer if we made the same decision that Oracle
did, here, but it doesn't really seem like the PostgreSQL way - or to
put that another way, it's really ugly and unprincipled. So I
recommend we decide for now that a partitioning column can't be null
and a partitioning expression can't evaluate to NULL. If it does,
ERROR.

OK, we can decide later if we want to handle NULLs somehow.

Thanks,
Amit

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

#157Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#148)
Re: Declarative partitioning - another take

Here are some comments on 0003 patch.
1. In ALTER TABLE documentation we should refer to CREATE TABLE documentation
for partition_bound_spec syntax, similar ADD table_constraint note.

2. I think, "of the target table" is missing after all the ... constraints
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints present in the target table.

3. I think, using "any of the" instead of "some" is preferred in the following
sentence. In the following sentence, we should use "such a constraint" instead
of "constraints" to avoid mixed singular and plural usage.
+ If some <literal>CHECK</literal> constraint of the table being attached

4. This construction is ambiguous. "are not considered" - considered where? for
what? Constraints on which object?
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY
KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.
5. What is a partition constraint? Do we define that term anywhere in the
documentation? If not we should define that term and then use it here.
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is

6. The paragraph following
+ A full table scan is performed on the table
being attached to check that seems to be confusing. It says that the
potentially expensive scan can be avoided by adding a constraint equivalent to
partition constraint. That seems to be wrong. The table will be scanned anyway,
when adding a valid constraint per ALTER TABLE ADD table_constraint
documentation. Instead you might want to say that the table scan will not be
carried out if the existing constraints imply the partition constraints.

7. You might want to specify the fate of range or lists covered by the
partition being detached in DETACH PARTITION section.

8. Since partition bound specification is more than a few words, you might want
to refer to the actual syntax in CREATE TABLE command.
+      <term><replaceable
class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>

9. I think, we need to use "may be scanned" instead of "is scanned", given that
some other part of the documentation talks about "avoiding" the table scan. May
be refer to that documentation to clarify the uncertainty.
+ Similarly, when attaching a new partition it is scanned to verify that

10. The same paragraph talks about multiple ALTER TABLE commands being run
together to avoid multiple table rewrites. Do we want to document whether
ATTACH/DETACH partition can be run with any other command or not.

11. ATTACH partition documentation is not talking about the unlogged or
temporary tables being attached to is logged or non-temporary. What is current
code doing about these cases? Do we allow such mixed partition hierarchy?
+ existing rows meet the partition constraint.

I will continue to look at the patch.

On Thu, Nov 24, 2016 at 4:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Rushabh,

On 2016/11/22 22:11, Rushabh Lathia wrote:

Hi Amit,

I was just reading through your patches and here are some quick review
comments
for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch.

Thanks for the review!

Review comments for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch:

1)
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
-            (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
-             relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-             relkind == RELKIND_COMPOSITE_TYPE || relkind ==
RELKIND_FOREIGN_TABLE))
+            (relkind == RELKIND_RELATION || relkind ==
RELKIND_PARTITIONED_TABLE ||
+             relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+             relkind == RELKIND_MATVIEW || relkind ==
RELKIND_COMPOSITE_TYPE ||
+             relkind == RELKIND_FOREIGN_TABLE))

You should add the RELKIND_PARTITIONED_TABLE at the end of if condition that
will make diff minimal. While reading through the patch I noticed that there
is inconsistency - someplace its been added at the end and at few places its
at the start. I think you can make add it at the end of condition and be
consistent with each place.

OK, done.

2)

+        /*
+         * We need to transform the raw parsetrees corresponding to
partition
+         * expressions into executable expression trees.  Like column
defaults
+         * and CHECK constraints, we could not have done the transformation
+         * earlier.
+         */

Additional space before "Like column defaults".

I think it's a common practice to add two spaces after a sentence-ending
period [https://www.gnu.org/prep/standards/html_node/Comments.html], which
it seems, is followed more or less regularly in the formatting of the
comments in the PostgreSQL source code.

3)
-    char        relkind;
+    char        relkind,
+                expected_relkind;

Newly added variable should be define separately with its type. Something
like:

char relkind;
+ char expected_relkind;

OK, done.

4)

a)
+    /* Prevent partitioned tables from becoming inheritance parents */
+    if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+        ereport(ERROR,
+                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                 errmsg("cannot inherit from partitioned table \"%s\"",
+                         parent->relname)));
+

need alignment for last line.

Fixed.

b)
+            atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
pelem->name);
+            if (!HeapTupleIsValid(atttuple))
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("column \"%s\" named in partition key does
not exist",
+                         pelem->name)));
+            attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+            if (attform->attnum <= 0)
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_COLUMN),
+                         errmsg("cannot use system column \"%s\" in
partition key",
+                         pelem->name)));

need alignment for last line of ereport

Fixed.

c)
+        /* Disallow ROW triggers on partitioned tables */
+        if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("\"%s\" is a partitioned table",
+                            RelationGetRelationName(rel)),
+              errdetail("Partitioned tables cannot have ROW triggers.")));

need alignment

Fixed.

5)

@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt
*stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+    cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;

I think adding bracket will look code more clear.

+ cxt.ispartitioned = (rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE);

Agreed, done.

6)

+ * RelationBuildPartitionKey
+ *        Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it
survives
+ * as long as the relcache.  To avoid leaking memory in that context in
case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.

extra space before "To avoid leaking memory"

Same thing I said above.

7)
+    /* variable-length fields start here, but we allow direct access to
partattrs */
+    int2vector        partattrs;        /* attribute numbers of columns in
the

Why partattrs is allow direct access - its not really clear from the
comments.

I have copied the comment from another catalog header file (a number of
catalog headers have the same comment). I updated the new file's comment
to say a little bit more; I wonder if the comment should be updated in
other files as well? However, I noticed that there are explanatory notes
elsewhere (for example, around the code that reads such a field from the
catalog) about why the first variable-length field of a catalog's tuple
(especially of type int2vector or oidvector) are directly accessible via
their C struct offsets.

I will continue reading more patch and testing functionality.. will share
the
comments as I have it.

Updated patches attached. I have also considered Robert's comments [1] as
follows and fixed a bug that Ashutosh reported [2]:

- Force partition key columns to be NOT NULL at the table creation time
if using range partitioning
- Use enum to represent the content of individual range datums (viz.
finite datum, -infinity, +infinity) in the relcache struct

Thanks,
Amit

[1]
/messages/by-id/CA+TgmoZ-feQsxc7U_JerM_AFChp3Qf6btK708SAe7M8Vdv5=jA@mail.gmail.com
[2]
/messages/by-id/306c85e9-c702-3742-eeff-9b7a40498afc@lab.ntt.co.jp

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#158Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#132)
Re: Declarative partitioning - another take

On 2016/11/17 20:27, Amit Langote wrote:

On 2016/11/16 4:21, Robert Haas wrote:

Have you done any performance testing on the tuple routing code?
Suppose we insert a million (or 10 million) tuples into an
unpartitioned table, a table with 10 partitions, a table with 100
partitions, a table with 1000 partitions, and a table that is
partitioned into 10 partitions each of which has 10 subpartitions.
Ideally, the partitioned cases would run almost as fast as the
unpartitioned case, but probably there will be some overhead.
However, it would be useful to know how much. Also, it would be
useful to set up the same cases with inheritance using a PL/pgsql ON
INSERT trigger for tuple routing and compare. Hopefully the tuple
routing code is far faster than a trigger, but we should make sure
that's the case and look for optimizations if not. Also, it would be
useful to know how much slower the tuple-mapping-required case is than
the no-tuple-mapping-required case.

OK, I will share the performance results soon.

Sorry about the delay; here are some numbers with the following
partitioning schema:

# plain table
create table plain (a date, b int, c int);

# partitioned table
create table ptab (a date, b int, c int) partition by range (a, b);

Partitions (not the full commands):

ptab_00001 for values from ('2016-11-29', 1) to ('2016-11-29', 1000);
ptab_00002 for values from ('2016-11-29', 1000) to ('2016-11-29', 2000);
...
ptab_00005 for values from ('2016-11-29', 4000) to ('2016-11-29', 5000);

ptab_00006 for values from ('2016-11-30', 1) to ('2016-11-30', 1000);
...
...
ptab_NNNNN for values from ('20XX-XX-XX', 4000) to ('20XX-XX-XX', 5000);

# inheritance partitioned table
create table itab (a date, b int, c int);
create table itab_00001 (
check part_check check (a = '2016-11-29' and b >= 1 and b < 1000)
) inherits (itab);
...
create table itab_00005 (
check part_check check (a = '2016-11-29' and b >= 4000 and b < 5000)
) inherits (itab);
create table itab_0006 (
check part_check check (a = '2016-11-30' and b >= 1and b < 1000)
) inherits (itab);
...
...
create table itab_NNNNN (
check part_check check (a = '2016-11-29' and b >= 4000 and b < 5000)
) inherits (itab);

The BR trigger (on itab) procedure as follows:

CREATE OR REPLACE FUNCTION itab_ins_trig()
RETURNS TRIGGER AS $$
DECLARE
partno text;
BEGIN
SELECT to_char((NEW.a - '2016-11-29'::date) * 5 + NEW.b / 1000 + 1,
'fm00000') INTO partno;
EXECUTE 'INSERT INTO itab_' || partno || ' SELECT $1.*' USING NEW;
RETURN NULL;
END; $$ LANGUAGE plpgsql;

Note that the tuple-routing procedure above assumes a fixed-stride range
partitioning scheme (shown as tg-direct-map below). In other cases, the
simplest approach involves defining a if-else ladder, which I tried too
(shown as tg-if-else below), but reporting times only for up to 200
partitions at most (I'm sure there might be ways to be smarter there
somehow, but I didn't; the point here may only be to compare the new
tuple-routing code's overhead vs. trigger overhead in the traditional method).

# All times in seconds (on my modestly-powerful development VM)
#
# nrows = 10,000,000 generated using:
#
# INSERT INTO $tab
# SELECT '$last'::date - ((s.id % $maxsecs + 1)::bigint || 's')::interval,
# (random() * 5000)::int % 4999 + 1,
# case s.id % 10
# when 0 then 'a'
# when 1 then 'b'
# when 2 then 'c'
# ...
# when 9 then 'j'
# end
# FROM generate_series(1, $nrows) s(id)
# ORDER BY random();
#
# The first item in the select list is basically a date that won't fall
# outside the defined partitions.

Time for a plain table = 98.1 sec

#part parted tg-direct-map tg-if-else
===== ====== ============= ==========
10 114.3 1483.3 742.4
50 112.5 1476.6 2016.8
100 117.1 1498.4 5386.1
500 125.3 1475.5 --
1000 129.9 1474.4 --
5000 137.5 1491.4 --
10000 154.7 1480.9 --

Then for a 2-level partitioned table with each of the above partitions
partitioned by list (c), with 10 sub-partitions each as follows:

ptab_NNNNN_a for values in ('a');
ptab_NNNNN_b for values in ('b');
...
ptab_NNNNN_k for values in ('j');

I didn't include the times for inheritance table with a routing trigger in
this case, as it seems that the results would look something like the above:

Time for a plain table = 98.1 sec

#part (sub-)parted
===== ============
10 127.0
50 152.3
100 156.6
500 191.8
1000 187.3

Regarding tuple-mapping-required vs no-tuple-mapping-required, all cases
currently require tuple-mapping, because the decision is based on the
result of comparing parent and partition TupleDesc using
equalTupleDescs(), which fails so quickly because TupleDesc.tdtypeid are
not the same. Anyway, I simply commented out the tuple-mapping statement
in ExecInsert() to observe just slightly improved numbers as follows
(comparing with numbers in the table just above):

#part (sub-)parted
===== =================
10 113.9 (vs. 127.0)
100 135.7 (vs. 156.6)
500 182.1 (vs. 191.8)

Thanks,
Amit

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

#159Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#157)
7 attachment(s)
Re: Declarative partitioning - another take

On 2016/11/28 23:42, Ashutosh Bapat wrote:

Here are some comments on 0003 patch.

Thanks for the review!

1. In ALTER TABLE documentation we should refer to CREATE TABLE documentation
for partition_bound_spec syntax, similar ADD table_constraint note.

That makes sense, done.

2. I think, "of the target table" is missing after all the ... constraints
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints present in the target table.

The "present in the target table" part at the end of the sentence is
supposed to mean just that, but I changed it to say "of the target table"
instead, replacing "present in" by "of".

3. I think, using "any of the" instead of "some" is preferred in the following
sentence. In the following sentence, we should use "such a constraint" instead
of "constraints" to avoid mixed singular and plural usage.
+ If some <literal>CHECK</literal> constraint of the table being attached

OK, done.

4. This construction is ambiguous. "are not considered" - considered where? for
what? Constraints on which object?
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY
KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.

That is essentially the same sentence as last sentence in the description
of INHERITS. But I moved it next to: "Also, it must have all the NOT NULL
and CHECK constraints present in the target table." Hopefully, that makes
it less ambiguous. It's supposed to mean that those constraints are not
considered for inheritance unlike NOT NULL and CHECK constraints.

5. What is a partition constraint? Do we define that term anywhere in the
documentation? If not we should define that term and then use it here.
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is

Partition constraint is an implicit constraint derived from the parent's
partition key and its partition bounds enforced whenever an action is
applied to a partition directly - such as, inserting/updating a row and
when attaching a table as partition. In fact, it also comes into play
during constraint exclusion when selecting from the parent table.

Do you think it's better to mention something like the above in the
description of CREATE TABLE PARTITION OF?

6. The paragraph following
+ A full table scan is performed on the table
being attached to check that seems to be confusing. It says that the
potentially expensive scan can be avoided by adding a constraint equivalent to
partition constraint. That seems to be wrong. The table will be scanned anyway,
when adding a valid constraint per ALTER TABLE ADD table_constraint
documentation. Instead you might want to say that the table scan will not be
carried out if the existing constraints imply the partition constraints.

Ah, I see the confusion. What's written is not supposed to mean that the
constraint be added after attaching the table as a partition, it's before.
It's right that the table will be scanned anyway to validate the
constraint, but at that point, it's not related to the partitioned table
in any way and hence does not affect it. As soon as the ATTACH PARTITION
command is executed, an exclusive lock will be acquired on the
parent/partitioned table, during which it's better to avoid any actions
that will take long - such as the validation scan. If the constraint
added *beforehand* to the table being attached is such that it would allow
only a subset or a strict subset of the rows that the partition constraint
will allow, then it is not necessary to *repeat* the scan and hence is
not. If it's not necessarily true that the constraints of the table being
attached will only allow a subset of a strict subset of the rows that the
partition constraint will allow, then the validation scan cannot be
avoided. IOW, the advice is to help avoid the scan while taking an
exclusive lock on the parent.

Anyway, I have modified some sentences to be slightly clearer, see if that
works for you.

7. You might want to specify the fate of range or lists covered by the
partition being detached in DETACH PARTITION section.

The partitioned table simply ceases to accept the rows falling in such a
range or a list. The detached partition turn into a standalone table
which happens to contain only the rows with values of certain columns
(partition keys) within the range or the list. However, there is no
explicit (catalogued) check constraint to that effect on the newly
standalone table, if that's what you were asking.

8. Since partition bound specification is more than a few words, you might want
to refer to the actual syntax in CREATE TABLE command.
+      <term><replaceable
class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>

OK, added a link to the CREATE TABLE page.

9. I think, we need to use "may be scanned" instead of "is scanned", given that
some other part of the documentation talks about "avoiding" the table scan. May
be refer to that documentation to clarify the uncertainty.
+ Similarly, when attaching a new partition it is scanned to verify that

Agreed, done.

10. The same paragraph talks about multiple ALTER TABLE commands being run
together to avoid multiple table rewrites. Do we want to document whether
ATTACH/DETACH partition can be run with any other command or not.

While currently ATTACH/DETACH can be run with any other ALTER TABLE
sub-command (much as ALTER TABLE INHERIT can), I think it's perhaps better
to prevent that. For example, following awkward-sounding thing happens:

alter table p attach partition p1 for values in (1, 2, 3), add b int;
ERROR: child table is missing column "b"

I have modified the patch so that the syntax does not allow such
combination of sub-commands. Also, moved ATTACH PARTITION and DETACH
PARTITION in the synopsis section from "where action is one of:" area to
above where various forms of ALTER TABLE are listed. Also specified down
below that ATTACH/DETACH PARTITION cannot be run in parallel with other
alterations.

11. ATTACH partition documentation is not talking about the unlogged or
temporary tables being attached to is logged or non-temporary. What is current
code doing about these cases? Do we allow such mixed partition hierarchy?

I tried to match the behavior of CREATE TABLE INHERITS() and ALTER TABLE
INHERIT

In ALTER TABLE child INHERIT parent case (as in CREATE TABLE child()
INHERITS(parent) case), failure occurs if the child is permanent rel and
parent temporary or if both are temporary, but if either parent or child
is of another session. The same happens in case of ALTER TABLE parent
ATTACH PARTITION child and CREATE TABLE child PARTITION OF parent,
respectively.

LOGGED-ness is not considered here at all.

Attached updated patches.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-19.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-19.patchDownload
From 6d4bd558e34ec928ad6c664cd269897b5f2ba192 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/7] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 |  112 ++++++-
 doc/src/sgml/ref/create_table.sgml         |   62 ++++
 src/backend/access/common/reloptions.c     |    2 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/aclchk.c               |    2 +
 src/backend/catalog/dependency.c           |   10 +-
 src/backend/catalog/heap.c                 |  165 +++++++++-
 src/backend/catalog/index.c                |    4 +-
 src/backend/catalog/objectaddress.c        |    5 +-
 src/backend/catalog/pg_constraint.c        |    2 +-
 src/backend/commands/analyze.c             |    6 +-
 src/backend/commands/copy.c                |    6 +
 src/backend/commands/indexcmds.c           |   24 +-
 src/backend/commands/lockcmds.c            |    2 +-
 src/backend/commands/policy.c              |    5 +-
 src/backend/commands/seclabel.c            |    3 +-
 src/backend/commands/sequence.c            |    3 +-
 src/backend/commands/tablecmds.c           |  509 +++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |   16 +-
 src/backend/commands/vacuum.c              |    3 +-
 src/backend/executor/execMain.c            |    2 +
 src/backend/executor/nodeModifyTable.c     |    3 +-
 src/backend/nodes/copyfuncs.c              |   34 ++
 src/backend/nodes/equalfuncs.c             |   29 ++
 src/backend/nodes/outfuncs.c               |   28 ++
 src/backend/parser/gram.y                  |  105 +++++-
 src/backend/parser/parse_agg.c             |   10 +
 src/backend/parser/parse_expr.c            |    5 +
 src/backend/parser/parse_func.c            |    3 +
 src/backend/parser/parse_utilcmd.c         |   73 ++++-
 src/backend/rewrite/rewriteDefine.c        |    3 +-
 src/backend/rewrite/rewriteHandler.c       |    3 +-
 src/backend/rewrite/rowsecurity.c          |    3 +-
 src/backend/utils/cache/relcache.c         |  265 ++++++++++++++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    3 +-
 src/include/catalog/heap.h                 |   10 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |    1 +
 src/include/catalog/pg_partitioned_table.h |   76 ++++
 src/include/commands/defrem.h              |    2 +
 src/include/nodes/nodes.h                  |    2 +
 src/include/nodes/parsenodes.h             |   29 ++
 src/include/parser/parse_node.h            |    3 +-
 src/include/pg_config_manual.h             |    5 +
 src/include/utils/rel.h                    |   68 ++++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |   46 +++
 src/test/regress/expected/create_table.out |  168 +++++++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |   32 ++
 src/test/regress/sql/create_table.sql      |  146 ++++++++
 52 files changed, 2047 insertions(+), 70 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 561e228..26e5ce8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64..eeb9d59 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</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> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</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> ]
@@ -314,6 +316,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+     <para>
+      When using range partitioning, a <literal>NOT NULL</literal> constraint
+      is added to each non-expression column in the partition key.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1411,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..3086021 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				break;
 			case ACL_OBJECT_SEQUENCE:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index f71d80f..0d94363 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1392,7 +1392,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1447,9 +1448,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..d7ce4ca 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1104,7 +1106,8 @@ heap_create_with_catalog(const char *relname,
 		if (IsBinaryUpgrade &&
 			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
 			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE ||
+			 relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1138,6 +1141,7 @@ heap_create_with_catalog(const char *relname,
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
+			case RELKIND_PARTITIONED_TABLE:
 				relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
 											  relnamespace);
 				break;
@@ -1182,7 +1186,8 @@ heap_create_with_catalog(const char *relname,
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
-							  relkind == RELKIND_COMPOSITE_TYPE))
+							  relkind == RELKIND_COMPOSITE_TYPE ||
+							  relkind == RELKIND_PARTITIONED_TABLE))
 		new_array_oid = AssignTypeArrayOid();
 
 	/*
@@ -1354,7 +1359,9 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE ||
+			   relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1801,6 +1808,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2046,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3018,3 +3042,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..f4afcd9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
-		onerel->rd_rel->relkind == RELKIND_MATVIEW)
+		onerel->rd_rel->relkind == RELKIND_MATVIEW ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
 		acquirefunc = acquire_sample_rows;
@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
-			childrel->rd_rel->relkind == RELKIND_MATVIEW)
+			childrel->rd_rel->relkind == RELKIND_MATVIEW ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			/* Regular table, so use the regular row acquisition function */
 			acquirefunc = acquire_sample_rows;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..2b0ae34 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 7e37108..1ab9030 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
-			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("referenced relation \"%s\" is not a table or foreign table",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6322fa7..826c476 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -252,6 +253,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("foreign table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a foreign table"),
 	gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,65 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts,
+						i;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+		List		   *cmds = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+
+		/* Force key columns to be NOT NULL when using range partitioning */
+		if (strategy == PARTITION_STRATEGY_RANGE)
+		{
+			for (i = 0; i < partnatts; i++)
+			{
+				AttrNumber	partattno = partattrs[i];
+				Form_pg_attribute attform = descriptor->attrs[partattno-1];
+
+				if (partattno != 0 && !attform->attnotnull)
+				{
+					/* Add a subcommand to make this one NOT NULL */
+					AlterTableCmd *cmd = makeNode(AlterTableCmd);
+
+					cmd->subtype = AT_SetNotNull;
+					cmd->name = pstrdup(NameStr(attform->attname));
+					cmds = lappend(cmds, cmd);
+				}
+			}
+
+			/*
+			 * Although, there cannot be any partitions yet, we still need to
+			 * pass true for recurse; ATPrepSetNotNull() complains if we don't
+			 */
+			if (cmds != NIL)
+				AlterTableInternal(RelationGetRelid(rel), cmds, true);
+		}
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -927,6 +1006,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
 	char		relkind;
+	char		expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1035,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1385,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1614,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2166,7 +2266,8 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
 		relkind != RELKIND_INDEX &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
@@ -4291,6 +4392,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,7 +4629,8 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
-			rel->rd_rel->relkind == RELKIND_MATVIEW)
+			rel->rd_rel->relkind == RELKIND_MATVIEW ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			if (origTypeName)
 				ereport(ERROR,
@@ -5250,6 +5353,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If the table is a range partitioned table, check that the column
+	 * is not in the partition key.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionKey	key = RelationGetPartitionKey(rel);
+		int				partnatts = get_partition_natts(key),
+						i;
+
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber	partattnum = get_partition_col_attnum(key, i);
+
+			if (partattnum == attnum)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("column \"%s\" is in range partition key",
+								colName)));
+		}
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5419,7 +5544,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
@@ -5692,6 +5818,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5893,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5938,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6469,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7904,6 +8112,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7934,6 +8143,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7949,7 +8171,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8979,6 +9202,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_PARTITIONED_TABLE:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -9440,6 +9664,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_VIEW:
@@ -9860,7 +10085,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10059,6 +10285,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10110,6 +10341,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11499,7 +11737,8 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
-		rel->rd_rel->relkind == RELKIND_MATVIEW)
+		rel->rd_rel->relkind == RELKIND_MATVIEW ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
 		AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
@@ -11948,7 +12187,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12105,7 +12344,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
@@ -12113,3 +12353,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+								pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+								pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..02e9693 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+					 errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..b1be2f7 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
-		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
+		onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		ereport(WARNING,
 				(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..29d5f57 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,7 +1886,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
-						relkind == RELKIND_MATVIEW)
+						relkind == RELKIND_MATVIEW ||
+						relkind == RELKIND_PARTITIONED_TABLE)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 367bc2e..1680fea 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -544,6 +546,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2811,69 +2817,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3418,6 +3430,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 17d1cbf..8a2bdf0 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2..fc896a2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -763,7 +829,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
@@ -1854,7 +1921,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				rel = heap_openrv(inh, AccessShareLock);
 				/* check user requested inheritance from valid relkind */
 				if (rel->rd_rel->relkind != RELKIND_RELATION &&
-					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+					rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..32e1328 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename,
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
-		event_relation->rd_rel->relkind != RELKIND_VIEW)
+		event_relation->rd_rel->relkind != RELKIND_VIEW &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or view",
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..bf4f098 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
-		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
+		target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Emit CTID so that executor can find the row to update or delete.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..7f3ba74 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -435,6 +439,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
 			return;
@@ -796,6 +801,236 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/*
+	 * We can rely on the first variable-length attribute being mapped to
+	 * the relevant field of the catalog's C struct, because all previous
+	 * attributes are non-nullable and fixed-length.
+	 */
+	attrs = form->partattrs.values;
+
+	/* But use the hard way to retrieve further variable-length attributes */
+	/* Operator class */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partexprs, &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		opclasstup;
+		Form_pg_opclass opclassform;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		opclasstup = SearchSysCache1(CLAOID,
+									 ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(opclasstup))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
+		key->partopfamily[i] = opclassform->opcfamily;
+		key->partopcintype[i] = opclassform->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(opclassform->opcfamily,
+								   opclassform->opcintype,
+								   opclassform->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(opclasstup);
+	}
+
+	ReleaseSysCache(tuple);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1285,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2286,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3229,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_MATVIEW ||
+		 relkind == RELKIND_PARTITIONED_TABLE))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3762,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4526,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5296,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..6a86c93 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -161,6 +161,7 @@ DESCR("");
 #define		  RELKIND_COMPOSITE_TYPE  'c'		/* composite type */
 #define		  RELKIND_FOREIGN_TABLE   'f'		/* foreign table */
 #define		  RELKIND_MATVIEW		  'm'		/* materialized view */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 
 #define		  RELPERSISTENCE_PERMANENT	'p'		/* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u'		/* unlogged permanent table */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..cec54ae
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/*
+	 * variable-length fields start here, but we allow direct access to
+	 * partattrs via the C struct.  That's because the first variable-length
+	 * field of a heap tuple can be reliably accessed using its C struct
+	 * offset, as previous fields are all non-nullable fixed-length fields.
+	 */
+	int2vector		partattrs;		/* each member of the array is the
+									 * attribute number of a partition key
+									 * column, or 0 if the column is actually
+									 * an expression */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 96885bb..58b1db9 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..df6fe13 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,49 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+ERROR:  column "a" is in range partition key
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..410d96b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,171 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+ attname | attnotnull 
+---------+------------
+ a       | t
+ b       | f
+ c       | t
+ d       | t
+(4 rows)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..ec61b02 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,35 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..b9489fc 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,149 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0002-psql-and-pg_dump-support-for-partitioned-tables-19.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-19.patchDownload
From 7f8697ca9bfd30d4fc0d79443566d9e6ae4c9c05 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/7] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  159 ++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |    4 +
 src/bin/pg_dump/pg_dump.c                  |   68 +++++++++++--
 src/bin/pg_dump/pg_dump.h                  |    2 +
 src/bin/psql/describe.c                    |   61 ++++++++---
 src/bin/psql/tab-complete.c                |    6 +-
 src/include/catalog/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   20 ++++-
 src/test/regress/sql/create_table.sql      |    6 +-
 10 files changed, 302 insertions(+), 27 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85..60fe794 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1415,6 +1417,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2ff60b9..7e2a65d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1239,9 +1239,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2098,6 +2099,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4972,7 +4976,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -4986,7 +4990,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5514,7 +5519,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6913,6 +6920,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14322,6 +14370,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14382,7 +14433,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
+			 tbinfo->relkind == RELKIND_FOREIGN_TABLE ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
 			{
@@ -14400,7 +14452,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14617,6 +14670,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f3e5977..e9849ef 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -312,6 +312,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..bc44ac5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -865,6 +865,7 @@ permissionsList(const char *pattern)
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
 					  gettext_noop("Schema"),
@@ -874,6 +875,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "c.relacl");
@@ -920,7 +922,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1566,8 +1568,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1748,7 +1758,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1756,14 +1766,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1902,7 +1931,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2461,7 +2490,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2665,7 +2694,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -2999,6 +3028,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 's' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3010,6 +3040,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("sequence"),
 					  gettext_noop("special"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 
@@ -3048,7 +3079,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6aa3f20..2765f4c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce..96e77ec 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1979,6 +1979,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 410d96b..02e0720 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -417,7 +417,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+ c      | text    |           | not null | 
+ d      | text    |           | not null | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index b9489fc..2af3214 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -411,7 +411,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
1.7.1

0003-Catalog-and-DDL-for-partitions-19.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-19.patchDownload
From 20fdcd449d21d374fd3478ab08bef9de68dc521c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/7] Catalog and DDL for partitions.

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

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

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

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

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 26e5ce8..7b7b5e8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..5e790f2 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -33,6 +33,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
 
@@ -166,6 +170,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       values or to reject null values.  You can only use <literal>SET
       NOT NULL</> when the column contains no null values.
      </para>
+
+     <para>
+      If this table is a partition, one cannot perform <literal>DROP NOT NULL</>
+      on a column if it is marked <literal>NOT NULL</literal> in the parent
+      table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,13 +714,63 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as a partition of the target table using the same syntax for
+      <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
+      <xref linkend="sql-createtable">.  The partition bound specification
+      must correspond to the partitioning strategy and partition key of the
+      target table.  The table to be attached must have all the same columns
+      as the target table and no more; moreover, the column types must also
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints of the target table.  Currently
+      <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered.
+      If any of the <literal>CHECK</literal> constraints of the table being
+      attached is marked <literal>NO INHERIT</literal>, the command will fail;
+      such a constraint must be recreated without the <literal>NO INHERIT</literal>
+      clause.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is
+      possible to avoid this scan by adding a valid <literal>CHECK</literal>
+      constraint to the table that would allow only the rows satisfying the
+      desired partition constraint before running this command.  It will be
+      determined using such a constraint that the table need not be scanned
+      to validate the partition constraint.  This does not work, however, if
+      any of the partition keys is an expression and the partition does not
+      accept <literal>NULL</literal> values.  If attaching a list partition
+      that will not accept <literal>NULL</literal> values, also add
+      <literal>NOT NULL</literal> constraint to the partition key column,
+      unless it's an expression.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
   <para>
    All the actions except <literal>RENAME</literal>,
-   <literal>SET TABLESPACE</literal> and <literal>SET SCHEMA</literal>
-   can be combined into
+   <literal>SET TABLESPACE</literal>, <literal>SET SCHEMA</literal>,
+   <literal>ATTACH PARTITION</literal>, and
+   <literal>DETACH PARTITION</literal> can be combined into
    a list of multiple alterations to apply in parallel.  For example, it
    is possible to add several columns and/or alter the type of several
    columns in a single command.  This is particularly useful with large
@@ -721,8 +781,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +998,25 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.  Refer to
+        <xref linkend="sql-createtable"> for more details on the syntax of the same.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1057,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition it may be scanned to verify that
+    existing rows meet the partition constraint.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1131,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1320,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
     ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
 </programlisting></para>
 
+  <para>
+   Attach a partition to range partitioned table:
+<programlisting>
+ALTER TABLE measurement
+    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Detach a partition from partitioned table:
+<programlisting>
+ALTER TABLE cities
+    DETACH PARTITION measurement_y2015m12;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033..5d0dcf5 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
+    SERVER server_07;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index eeb9d59..27a391e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +247,50 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partition key of the parent table, and must not overlap with
+      any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1429,7 +1488,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d7ce4ca..9729bfa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1766,6 +1774,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1773,6 +1783,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1863,6 +1888,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2469,8 +2500,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2515,10 +2549,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3177,3 +3225,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..f439c43
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1567 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Information about bounds of a partitioned relation
+ *
+ * A list partition datum that is known to be NULL is never put into the
+ * datums array, instead it is tracked using has_null and null_index fields.
+ *
+ * In case of range partitioning, ndatums is far less than 2 * nparts, because
+ * a partition's upper bound and the next partition's lower bound are same
+ * in most common cases, and we only store one of them.
+ *
+ * In case of list partitioning, the indexes array stores one entry for every
+ * datum, which is the index of the partition that accepts a given datum.
+ * Wheareas, in case of range partitioning, it stores one entry per distinct
+ * range datum, which is the index of the partition of which a given datum
+ * is an upper bound.
+ */
+
+/* Ternary value to represent what's contained in a range bound datum */
+typedef enum RangeDatumContent
+{
+	RANGE_DATUM_FINITE = 0,		/* actual datum stored elsewhere */
+	RANGE_DATUM_NEG_INF,		/* negative infinity */
+	RANGE_DATUM_POS_INF			/* positive infinity */
+} RangeDatumContent;
+
+typedef struct PartitionBoundInfoData
+{
+	char		strategy;		/* list or range bounds? */
+	int			ndatums;		/* Length of the datums following array */
+	Datum	  **datums;			/* Array of datum-tuples with key->partnatts
+								 * datums each */
+	RangeDatumContent **content;	/* what's contained in each range bound
+									 * datum? (see the above enum); NULL for
+									 * list partitioned tables */
+	int		   *indexes;		/* Partition indexes; one entry per member of
+								 * the datums array (plus one if range
+								 * partitioned table) */
+	bool		has_null;		/* Is there a null-accepting partition? false
+								 * for range partitioned tables */
+	int			null_index;		/* Index of the null-accepting partition; -1
+								 * for range partitioned tables */
+} PartitionBoundInfoData;
+
+/*
+ * When qsort'ing partition bounds after reading from the catalog, each bound
+ * is represented with one of the following structs.
+ */
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	int		index;
+	Datum	value;
+} PartitionListValue;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;		/* range bound datums */
+	RangeDatumContent *content;	/* what's contained in each datum?*/
+	bool	lower;		/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static int32 partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2);
+
+static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound);
+static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	int		ndatums;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	bool	found_null = false;
+	int		null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null = false;
+				null_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					if (spec->strategy != PARTITION_STRATEGY_LIST)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null)
+								elog(ERROR, "found null more than once");
+							found_null = true;
+							null_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				ndatums = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **) palloc(ndatums *
+											sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp, (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+				bool   *distinct_indexes;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+				distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					if (spec->strategy != PARTITION_STRATEGY_RANGE)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					lower = make_one_range_bound(key, i, spec->lowerdatums,
+												 true);
+					upper = make_one_range_bound(key, i, spec->upperdatums,
+												 false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				ndatums = 0;
+				prev = NULL;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+					bool	is_distinct = false;
+					int		j;
+
+					/* Is current bound is distinct from the previous? */
+					for (j = 0; j < key->partnatts; j++)
+					{
+						Datum	cmpval;
+
+						if (prev == NULL)
+						{
+							is_distinct = true;
+							break;
+						}
+
+						/*
+						 * If either of them has infinite element, we can't
+						 * equate them.  Even when both are infinite, they'd
+						 * have opposite signs, because only one of cur and
+						 * prev is a lower bound).
+						 */
+						if (cur->content[j] != RANGE_DATUM_FINITE ||
+							prev->content[j] != RANGE_DATUM_FINITE)
+						{
+							is_distinct = true;
+							break;
+						}
+						cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+												   key->partcollation[j],
+												   cur->datums[j],
+												   prev->datums[j]);
+						if (DatumGetInt32(cmpval) != 0)
+						{
+							is_distinct = true;
+							break;
+						}
+					}
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.  Also, store if the index i contains
+					 * a distinct bound that we'd like put in the relcache
+					 * array.
+					 */
+					if (is_distinct)
+					{
+						distinct_indexes[i] = true;
+						ndatums++;
+					}
+					else
+						distinct_indexes[i] = false;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				rbounds = (PartitionRangeBound **) palloc(ndatums *
+											sizeof(PartitionRangeBound *));
+				k = 0;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					if (distinct_indexes[i])
+						rbounds[k++] = all_bounds[i];
+				}
+				Assert(k == ndatums);
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+									palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->ndatums = ndatums;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				boundinfo->has_null = found_null;
+				boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+				/*
+				 * Copy values.  Indexes of individual values are mapped to
+				 * canonical values so that they match for any two list
+				 * partitioned tables with same number of partitions and same
+				 * lists per partition.  One way to canonicalize is to assign
+				 * the index in all_values[] of the smallest value of each
+				 * partition, as the index of all of the partition's values.
+				 */
+				for (i = 0; i < ndatums; i++)
+				{
+					boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+					boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If the old index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					boundinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * If null-accepting partition has no mapped index yet, assign
+				 * one.  This could happen if such partition accepts only null
+				 * and hence not covered in the above loop which only handled
+				 * non-null values.
+				 */
+				if (found_null)
+				{
+					Assert(null_index >= 0);
+					if (mapping[null_index] == -1)
+						mapping[null_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null)
+					boundinfo->null_index = mapping[null_index];
+				else
+					boundinfo->null_index = -1;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				boundinfo->content = (RangeDatumContent **) palloc(ndatums *
+												 sizeof(RangeDatumContent *));
+				boundinfo->indexes = (int *) palloc((ndatums+1) *
+													sizeof(int));
+
+				for (i = 0; i < ndatums; i++)
+				{
+					int		j;
+
+					boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+															sizeof(Datum));
+					boundinfo->content[i] = (RangeDatumContent *)
+												palloc(key->partnatts *
+												   sizeof(RangeDatumContent));
+					for (j = 0; j < key->partnatts; j++)
+					{
+						if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
+							boundinfo->datums[i][j] =
+											datumCopy(rbounds[i]->datums[j],
+													  key->parttypbyval[j],
+													  key->parttyplen[j]);
+						/* Remember, we are storing the tri-state value. */
+						boundinfo->content[i][j] = rbounds[i]->content[j];
+					}
+
+					/*
+					 * There is no mapping for invalid indexes.
+					 *
+					 * Any lower bounds in the rbounds array have invalid
+					 * indexes assigned, because the values between the
+					 * previous bound (if there is one) and this (lower)
+					 * bound are not part of the range of any existing
+					 * partition.
+					 */
+					if (rbounds[i]->lower)
+						boundinfo->indexes[i] = -1;
+					else
+					{
+						int		orig_index = rbounds[i]->index;
+
+						/* If the old index is has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[orig_index];
+					}
+				}
+				boundinfo->indexes[i] = -1;
+				break;
+			}
+		}
+
+		result->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int		i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->has_null != b2->has_null)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	for (i = 0; i < b1->ndatums; i++)
+	{
+		int		j;
+
+		for (j = 0; j < key->partnatts; j++)
+		{
+			int32	cmpval;
+
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+													 key->partcollation[0],
+													 b1->datums[i][j],
+													 b2->datums[i][j]));
+			if (cmpval != 0)
+				return false;
+
+			/* Range partitions can have infinite datums */
+			if (b1->content != NULL && b1->content[i][j] != b2->content[i][j])
+				return false;
+		}
+
+		if(b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+
+	/* There are ndatums+1 indexes in case of range partitions */
+	if (key->strategy == PARTITION_STRATEGY_RANGE &&
+		b1->indexes[i] != b2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				ListCell   *cell;
+
+				Assert(boundinfo &&
+					   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
+					   (boundinfo->ndatums > 0 || boundinfo->has_null));
+
+				foreach (cell, spec->listdatums)
+				{
+					Const  *val = lfirst(cell);
+
+					if (!val->constisnull)
+					{
+						int		offset;
+						bool	equal;
+
+						offset = partition_bound_bsearch(key, boundinfo,
+														 &val->constvalue,
+														 true, &equal);
+						if (offset >= 0 && equal)
+						{
+							overlap = true;
+							with = boundinfo->indexes[offset];
+							break;
+						}
+					}
+					else if (boundinfo->has_null)
+					{
+						overlap = true;
+						with = boundinfo->null_index;
+						break;
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+			upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified lower and upper bounds
+			 */
+			if (partition_rbound_cmp(key, lower->datums, lower->content, true,
+									 upper) >= 0)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				int		  off1, off2;
+				bool	  equal = false;
+
+				Assert(boundinfo && boundinfo->ndatums > 0 &&
+					   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than or equal with the new lower bound.
+				 */
+				off1 = partition_bound_bsearch(key, boundinfo, lower, true,
+											   &equal);
+
+				/*
+				 * If equal has been set to true, that means the new lower
+				 * bound is found to be equal with the bound at off1, which
+				 * clearly means an overlap with the partition at index
+				 * off1+1).
+				 *
+				 * Otherwise, check if there is a "gap" that could be occupied
+				 * by the new partition.  In case of a gap, the new upper
+				 * bound should not cross past the upper boundary of the gap,
+				 * that is, off2 == off1 should be true.
+				 */
+				if (!equal && boundinfo->indexes[off1+1] < 0)
+				{
+					off2 = partition_bound_bsearch(key, boundinfo, upper,
+												   true, &equal);
+
+					if (equal || off1 != off2)
+					{
+						overlap = true;
+						with = boundinfo->indexes[off2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					with = boundinfo->indexes[off1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *keyCol;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		keyCol = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		keyCol = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * We must remove any NULL value in the list; we handle it separately
+	 * below.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/*
+		 * Gin up a col IS NOT NULL test that will be AND'd with other
+		 * expressions
+		 */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) keyCol;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/*
+		 * Gin up a col IS NULL test that will be OR'd with other expressions
+		 */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) keyCol;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr containing this partition's values */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(keyCol, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key, emitting an OpExpr for each using
+	 * the corresponding lower and upper datums as constant operands.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *keyCol;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		Oid				operoid;
+		NullTest	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			keyCol = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/*
+		 * Emit a IS NOT NULL expression for non-Var keys, because whereas
+		 * simple attributes are covered by NOT NULL constraints, expression
+		 * keys are still nullable which is not acceptable in case of range
+		 * partitioning.
+		 */
+		if (!IsA(keyCol, Var))
+		{
+			nulltest = makeNode(NullTest);
+			nulltest->arg = (Expr *) keyCol;
+			nulltest->nulltesttype = IS_NOT_NULL;
+			nulltest->argisrow = false;
+			nulltest->location = -1;
+			result = lappend(result, nulltest);
+		}
+
+		/*
+		 * Stop at this column if either of lower or upper datum is infinite,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (keyCol = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create the test expression */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* This can never be, but it's better to make sure */
+				if (i == key->partnatts - 1)
+					elog(ERROR, "invalid range bound specification");
+
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) keyCol,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val != upper_val.  Emit expressions
+		 * (keyCol >= lower_val) and (keyCol < upper_val), then stop.
+		 */
+		if (lower_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTGreaterEqualStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) keyCol,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTLessStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) keyCol,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * opreator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	if (!rel->rd_rel->relispartition)	/* should not happen */
+		elog(ERROR, "relation \"%s\" has relispartition = false",
+					RelationGetRelationName(rel));
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)		/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+					RelationGetRelationName(rel));
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			val1 = (*(const PartitionListValue **) a)->value,
+					val2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * make_one_range_bound
+ *
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.  Made into a function
+ * because there are multiple sites that want to use this facility.
+ */
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->content = (RangeDatumContent *) palloc0(key->partnatts *
+												sizeof(RangeDatumContent));
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		/* What's contained in this range datum? */
+		bound->content[i] = !datum->infinite
+								? RANGE_DATUM_FINITE
+								: (lower ? RANGE_DATUM_NEG_INF
+										 : RANGE_DATUM_POS_INF);
+
+		if (bound->content[i] == RANGE_DATUM_FINITE)
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
+}
+
+/*
+ * partition_rbound_cmp
+ * 
+ * Return for two range bounds whether the 1st one (specified in datum1,
+ * content1, and lower1) is <=, =, >= the bound specified in *b2
+ */
+static int32
+partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2)
+{
+	int32	cmpval;
+	int		i;
+	Datum  *datums2 = b2->datums;
+	RangeDatumContent *content2 = b2->content;
+	bool	lower2 = b2->lower;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (content1[i] != RANGE_DATUM_FINITE &&
+			content2[i] != RANGE_DATUM_FINITE)
+			/*
+			 * Both are infinity, so they are equal unless one is
+			 * negative infinity and other positive (or vice versa)
+			 */
+			return content1[i] == content2[i] ? 0
+								: (content1[i] < content2[i] ? -1 : 1);
+		else if (content1[i] != RANGE_DATUM_FINITE)
+			return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+		else if (content2[i] != RANGE_DATUM_FINITE)
+			return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i],
+												 datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If
+	 * they compare equal though, we still have to consider whether
+	 * the boundaries are inclusive or exclusive.  Exclusive one is
+	 * considered smaller of the two.
+	 */
+	if (cmpval == 0 && lower1 != lower2)
+		cmpval = lower1 ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
+ * partition_bound_cmp
+ * 
+ * Return whether the bound at offset in boundinfo is <=, =, >= the argument
+ * specified in *probe.
+ */
+static int32
+partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound)
+{
+	Datum  *bound_datums = boundinfo->datums[offset];
+	int32	cmpval;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   bound_datums[0],
+										   *(Datum *) probe));
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			RangeDatumContent  *content = boundinfo->content[offset];
+
+			if (probe_is_bound)
+			{
+				/*
+				 * We need to pass whether the existing bound is a lower
+				 * bound, so that two equal-valued lower and upper bounds are
+				 * not regarded equal.
+				 */
+				bool	lower = boundinfo->indexes[offset] < 0;
+
+				cmpval = partition_rbound_cmp(key,
+											  bound_datums, content, lower,
+											  (PartitionRangeBound *) probe);
+			}
+
+			break;
+		}
+	}
+
+	return cmpval;
+}
+
+/*
+ * Binary search on a collection of partition bounds. Returns greatest index
+ * of bound in array boundinfo->datums which is less or equal with *probe.
+ * If all bounds in the array are greater than *probe, -1 is returned.
+ *
+ * *probe could either be a partition bound or a Datum array representing
+ * the partition key of a tuple being routed; probe_is_bound tells which.
+ * We pass that down to the comparison function so that it can interpret the
+ * contents of *probe accordingly.
+ *
+ * *is_equal is set to whether the bound at the returned index is equal with
+ * *probe.
+ */
+static int
+partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal)
+{
+	int		lo,
+			hi,
+			mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_bound_cmp(key, boundinfo, mid, probe,
+									 probe_is_bound);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 1ab9030..d953b44 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 826c476..71ca739 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -65,6 +66,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
 	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;		/* if above is true */
+	List	   *partition_constraint; /* for attach partition validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
+							 stmt->partbound != NULL,
 							 &inheritOids, &old_constraints, &parentOidCount);
 
 	/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	descriptor = BuildDescForRelation(schema);
 
 	/*
-	 * Notice that we allow OIDs here only for plain tables, even though some
-	 * other relkinds can support them.  This is necessary because the
-	 * default_with_oids GUC must apply only to plain tables and not any other
-	 * relkind; doing otherwise would break existing pg_dump files.  We could
-	 * allow explicit "WITH OIDS" while not allowing default_with_oids to
-	 * affect other relkinds, but it would complicate interpretOidsOption().
+	 * Notice that we allow OIDs here only for plain tables and partitioned
+	 * tables, even though some other relkinds can support them.  This is
+	 * necessary because the default_with_oids GUC must apply only to plain
+	 * tables and not any other relkind; doing otherwise would break existing
+	 * pg_dump files.  We could allow explicit "WITH OIDS" while not allowing
+	 * default_with_oids to affect other relkinds, but it would complicate
+	 * interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
 									   (relkind == RELKIND_RELATION ||
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parent;
+
+		/* Already have strong enough lock on the parent */
+		parent = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partition key of parentRel, so it better have one.
+		 */
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parent))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parent, stmt->partbound);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parent, bound);
+		heap_close(parent, NoLock);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partition key information into the catalog.
@@ -1146,6 +1219,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1452,6 +1529,7 @@ storage_name(char c)
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of names (as RangeVar nodes) of parent relations.
  * 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
  *
  * Output arguments:
  * 'supOids' receives a list of the OIDs of the parent relations.
@@ -1503,7 +1581,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1513,6 +1592,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema = NIL;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1532,6 +1612,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 						MaxHeapAttributeNumber)));
 
 	/*
+	 * In case of a partition, there are no new column definitions, only
+	 * dummy ColumnDefs created for column constraints.  We merge these
+	 * constraints inherited from the parent.
+	 */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
+	/*
 	 * Check for duplicate names in the explicit list of attributes.
 	 *
 	 * Although we might consider merging such entries in the same way that we
@@ -1611,18 +1702,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * We do not allow partitioned tables and partitions to participate
+		 * in regular inheritance.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
 
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1632,7 +1740,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create a permanent relation as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1640,7 +1750,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1887,7 +1999,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	/*
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.
+	 * columns into the inherited schema list.  Although, we never have any
+	 * explicitly declared columns if the table is a partition.
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1916,6 +2029,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
+				 * Partitions have only one parent, so conflict should never
+				 * occur
+				 */
+				Assert(!is_partition);
+
+				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
 				 */
@@ -1997,6 +2116,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have the column definition list for a partition, we can
+	 * check whether the columns referenced in column option specifications
+	 * actually exist.  Also, we merge the options into the corresponding
+	 * column definitions.
+	 */
+	if (is_partition && list_length(saved_schema) > 0)
+	{
+		schema = list_concat(schema, saved_schema);
+
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
+
+			/*
+			 * Partition column option that does not belong to a column from
+			 * the parent.  This works because the columns from the parent
+			 * come first in the list (see above).
+			 */
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" does not exist",
+							coldef->colname)));
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);		/* need to save it in case
+													 * we delete it */
+
+				if (strcmp(coldef->colname, restdef->colname) == 0)
+				{
+					/*
+					 * merge the column options into the column from the
+					 * parent
+					 */
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					list_delete_cell(schema, rest, prev);
+				}
+				prev = rest;
+				rest = next;
+			}
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -3158,6 +3327,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);
@@ -3269,12 +3443,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3475,6 +3651,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);
@@ -3545,7 +3727,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, we did
+		 * not modify anything about it that will change its toasting
+		 * requirement, so no need to check.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  tab->partition_constraint == NIL) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3794,6 +3983,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);
@@ -3979,7 +4174,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			if (tab->constraints != NIL || tab->new_notnull)
+			if (tab->constraints != NIL || tab->new_notnull ||
+				tab->partition_constraint != NIL)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4059,6 +4255,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4123,6 +4320,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_constraint)
+	{
+		needscan = true;
+		partqualstate = (List *)
+						ExecPrepareExpr((Expr *) tab->partition_constraint,
+										estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4312,6 +4518,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4509,7 +4720,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4831,6 +5043,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5277,6 +5494,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5352,6 +5583,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * If the table is a range partitioned table, check that the column
 	 * is not in the partition key.
@@ -5406,6 +5654,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5965,6 +6228,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7985,6 +8257,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10286,6 +10568,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10298,12 +10585,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;
 
@@ -10348,37 +10630,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from partitioned table \"%s\"",
 						parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10413,6 +10669,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);
 
@@ -10427,16 +10746,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;
 }
 
 /*
@@ -10487,7 +10798,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
@@ -10505,12 +10816,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		child_is_partition = false;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10558,6 +10874,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (child_is_partition)
+			{
+				Assert(childatt->attinhcount == 1);
+				childatt->attislocal = false;
+			}
+
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10580,7 +10908,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.
@@ -10599,10 +10927,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		child_is_partition = false;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10679,6 +11012,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (child_is_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10703,6 +11048,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10716,13 +11101,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];
@@ -10731,19 +11114,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		child_is_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10753,7 +11128,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);
 
@@ -10774,11 +11149,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	if (!found)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_TABLE),
-				 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-						RelationGetRelationName(parent_rel),
-						RelationGetRelationName(rel))));
+	{
+		if (child_is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+							RelationGetRelationName(child_rel),
+							RelationGetRelationName(parent_rel))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+							RelationGetRelationName(parent_rel),
+							RelationGetRelationName(child_rel))));
+	}
 
 	/*
 	 * Search through child columns looking for ones matching parent rel
@@ -10787,7 +11171,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)))
@@ -10849,7 +11233,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);
 
@@ -10880,7 +11264,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)
@@ -10892,30 +11276,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;
 }
 
 /*
@@ -12600,3 +12974,454 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	Relation	attachRel,
+				catalog;
+	List	   *childrels;
+	TupleConstr	*attachRel_constr;
+	List	   *partConstraint,
+			   *existConstraint;
+	SysScanDesc scan;
+	ScanKeyData skey;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	bool		skip_validate = false;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * Table being attached should not already be part of inheritance; either
+	 * as a child table...
+	 */
+	catalog = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or as a parent table (except the case when it is partitioned) */
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+							  1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(catalog, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* Temp parent cannot have a partition that is itself not a temp */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		attachRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
+						RelationGetRelationName(rel))));
+
+	/* If the parent is temp, it must belong to this session */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!rel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach as partition of temporary relation of another session")));
+
+	/* Ditto for the partition */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		 errmsg("cannot attach temporary relation of another session as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel), rel,
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * Generate partition constraint from the partition bound specification.
+	 * If the parent itself is a partition, make sure to include its
+	 * constraint as well.
+	 */
+	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+														 cmd->bound),
+								 RelationGetPartitionQual(rel, true));
+	partConstraint = (List *) eval_const_expressions(NULL,
+													 (Node *) partConstraint);
+	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+	partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+	/*
+	 * Check if we can do away with having to scan the table being attached
+	 * to validate the partition constraint, by *proving* that the existing
+	 * constraints of the table *imply* the partition predicate.  We include
+	 * the table's check constraints and NOT NULL constraints in the list of
+	 * clauses passed to predicate_implied_by().
+	 *
+	 * There is a case in which we cannot rely on just the result of the
+	 * proof.
+	 */
+	tupleDesc = RelationGetDescr(attachRel);
+	attachRel_constr = tupleDesc->constr;
+	existConstraint = NIL;
+	if (attachRel_constr > 0)
+	{
+		int			num_check = attachRel_constr->num_check;
+		int			i;
+		Bitmapset  *not_null_attrs = NULL;
+		List	   *part_constr;
+		ListCell   *lc;
+		bool		partition_accepts_null = true;
+		int			partnatts;
+
+		if (attachRel_constr->has_not_null)
+		{
+			int			natts = attachRel->rd_att->natts;
+
+			for (i = 1; i <= natts; i++)
+			{
+				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+				if (att->attnotnull && !att->attisdropped)
+				{
+					NullTest   *ntest = makeNode(NullTest);
+
+					ntest->arg = (Expr *) makeVar(1,
+												  i,
+												  att->atttypid,
+												  att->atttypmod,
+												  att->attcollation,
+												  0);
+					ntest->nulltesttype = IS_NOT_NULL;
+
+					/*
+					 * argisrow=false is correct even for a composite column,
+					 * because attnotnull does not represent a SQL-spec IS NOT
+					 * NULL test in such a case, just IS DISTINCT FROM NULL.
+					 */
+					ntest->argisrow = false;
+					ntest->location = -1;
+					existConstraint = lappend(existConstraint, ntest);
+					not_null_attrs = bms_add_member(not_null_attrs, i);
+				}
+			}
+		}
+
+		for (i = 0; i < num_check; i++)
+		{
+			Node	   *cexpr;
+
+			/*
+			 * If this constraint hasn't been fully validated yet, we must
+			 * ignore it here.
+			 */
+			if (!attachRel_constr->check[i].ccvalid)
+				continue;
+
+			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+			/*
+			 * Run each expression through const-simplification and
+			 * canonicalization.  It is necessary, because we will be
+			 * comparing it to similarly-processed qual clauses, and may fail
+			 * to detect valid matches without this.
+			 */
+			cexpr = eval_const_expressions(NULL, cexpr);
+			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+			existConstraint = list_concat(existConstraint,
+										  make_ands_implicit((Expr *) cexpr));
+		}
+
+		existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+		/* And away we go ... */
+		if (predicate_implied_by(partConstraint, existConstraint))
+			skip_validate = true;
+
+		/*
+		 * We choose to err on the safer side, ie, give up on skipping the
+		 * the validation scan, if the partition key column doesn't have
+		 * the NOT NULL constraint and the table is to become a list partition
+		 * that does not accept nulls.  In this case, the partition predicate
+		 * (partConstraint) does include an 'key IS NOT NULL' expression,
+		 * however, because of the way predicate_implied_by_simple_clause()
+		 * is designed to handle IS NOT NULL predicates in the absence of a
+		 * IS NOT NULL clause, we cannot rely on just the above proof.
+		 *
+		 * That is not an issue in case of a range partition, because if there
+		 * were no NOT NULL constraint defined on the key columns, an error
+		 * would be thrown before we get here anyway.  That is not true,
+		 * however, if any of the partition keys is an expression, which is
+		 * handled below.
+		 */
+		part_constr = linitial(partConstraint);
+		part_constr = make_ands_implicit((Expr *) part_constr);
+
+		/*
+		 * part_constr contains an IS NOT NULL expression, if this is a list
+		 * partition that does not accept nulls (in fact, also if this is a
+		 * range partition and some partition key is an expression, but we
+		 * never skip validation in that case anyway; see below)
+		 */
+		foreach(lc, part_constr)
+		{
+			Node *expr = lfirst(lc);
+
+			if (IsA(expr, NullTest) &&
+				((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
+			{
+				partition_accepts_null = false;
+				break;
+			}
+		}
+
+		partnatts = get_partition_natts(key);
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber	partattno;
+
+			partattno = get_partition_col_attnum(key, i);
+
+			/* If partition key is an expression, must not skip validation */
+			if (!partition_accepts_null &&
+				(partattno == 0 ||
+				 !bms_is_member(partattno, not_null_attrs)))
+				skip_validate = false;
+		}
+	}
+
+	if (skip_validate)
+		elog(NOTICE, "skipping scan to validate partition constraint");
+
+	/*
+	 * Set up to have the table to be scanned to validate the partition
+	 * constraint (see partConstraint above).  If it's a partitioned table,
+	 * we instead schdule its leaf partitions to be scanned instead.
+	 */
+	if (!skip_validate)
+	{
+		List	   *all_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+											 AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, all_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+
+			/* Lock already taken */
+			if (part_relid != RelationGetRelid(attachRel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = attachRel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel != attachRel &&
+				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, part_rel);
+
+			constr = linitial(partConstraint);
+			tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+			/* keep our lock until commit */
+			if (part_rel != attachRel)
+				heap_close(part_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that the new partition is now included
+	 * in rel's partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_NODE_FIELD(lowerdatums);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1680fea..fabe80d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -278,7 +279,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	add_drop opt_asc_desc opt_nulls_order
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
-	   replica_identity
+	   replica_identity partition_cmd
 %type <list>	alter_table_cmds alter_type_cmds
 
 %type <dbehavior>	opt_drop_behavior
@@ -550,6 +551,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -575,7 +583,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
@@ -591,7 +599,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
@@ -1792,6 +1801,24 @@ AlterTableStmt:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
+		|	ALTER TABLE relation_expr partition_cmd
+				{
+					AlterTableStmt *n = makeNode(AlterTableStmt);
+					n->relation = $3;
+					n->cmds = list_make1($4);
+					n->relkind = OBJECT_TABLE;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+		|	ALTER TABLE IF_P EXISTS relation_expr partition_cmd
+				{
+					AlterTableStmt *n = makeNode(AlterTableStmt);
+					n->relation = $5;
+					n->cmds = list_make1($6);
+					n->relkind = OBJECT_TABLE;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 		|	ALTER TABLE ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
 				{
 					AlterTableMoveAllStmt *n =
@@ -1937,6 +1964,34 @@ alter_table_cmds:
 			| alter_table_cmds ',' alter_table_cmd	{ $$ = lappend($1, $3); }
 		;
 
+partition_cmd:
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			ATTACH PARTITION qualified_name ForValues
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+		;
+
 alter_table_cmd:
 			/* ALTER TABLE <name> ADD <coldef> */
 			ADD_P columnDef
@@ -2472,6 +2527,73 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerdatums = $5;
+					n->upperdatums = $9;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2889,6 +3011,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2934,6 +3094,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2956,6 +3121,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2967,6 +3143,28 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+		TableConstraint					{ $$ = $1; }
+		|	ColId ColQualList
+			{
+				ColumnDef *n = makeNode(ColumnDef);
+				n->colname = $1;
+				n->typeName = NULL;
+				n->inhcount = 0;
+				n->is_local = true;
+				n->is_not_null = false;
+				n->is_from_type = false;
+				n->storage = 0;
+				n->raw_default = NULL;
+				n->cooked_default = NULL;
+				n->collOid = InvalidOid;
+				SplitColQualList($2, &n->constraints, &n->collClause,
+								 yyscanner);
+				n->location = @1;
+				$$ = (Node *) n;
+			}
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4554,6 +4752,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;
+				}
 		;
 
 /*****************************************************************************
@@ -13741,6 +13981,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13787,6 +14028,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index fc896a2..e64f4a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2581,6 +2586,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2663,6 +2669,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3027,3 +3046,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partition key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("FROM must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("TO must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+									*rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7f3ba74..c958092 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2);
 
 
 /*
@@ -1158,6 +1161,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2)
+{
+	int		i;
+
+	if (partdesc1 != NULL)
+	{
+		if (partdesc2 == NULL)
+			return false;
+		if (partdesc1->nparts != partdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || partdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < partdesc1->nparts; i++)
+		{
+			if (partdesc1->oids[i] != partdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is local to partition.c.
+		 */
+		if (partdesc1->boundinfo != NULL)
+		{
+			if (partdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, partdesc1->boundinfo,
+											 partdesc2->boundinfo))
+				return false;
+		}
+		else if (partdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (partdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1285,13 +1340,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2288,6 +2348,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2436,11 +2500,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2451,6 +2516,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);
@@ -2481,6 +2547,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
@@ -2536,6 +2605,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 */
@@ -3770,6 +3846,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5298,6 +5377,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..70d8325
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	PartitionBoundInfo	boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo p1, PartitionBoundInfo p2);
+
+extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 6a86c93..a61b7a2 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d30c82b..427eff2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/*
+	 * Range partition lower and upper bounds; each member of the lists
+	 * is a PartitionRangeDatum (see below).
+	 */
+	List	   *lowerdatums;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1577,7 +1622,9 @@ typedef enum AlterTableType
 	AT_DisableRowSecurity,		/* DISABLE ROW SECURITY */
 	AT_ForceRowSecurity,		/* FORCE ROW SECURITY */
 	AT_NoForceRowSecurity,		/* NO FORCE ROW SECURITY */
-	AT_GenericOptions			/* OPTIONS (...) */
+	AT_GenericOptions,			/* OPTIONS (...) */
+	AT_AttachPartition,			/* ATTACH PARTITION */
+	AT_DetachPartition			/* DETACH PARTITION */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de3..cd7ea1d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index df6fe13..09cc193 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3020,3 +3020,300 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+--
+-- ATTACH PARTITION
+--
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check any TEMP-ness
+CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE perm_part (a int);
+ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
+ERROR:  cannot attach a permanent relation as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted, perm_part;
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+NOTICE:  skipping scan to validate partition constraint
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+NOTICE:  skipping scan to validate partition constraint
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+NOTICE:  skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  "part_2" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted2" is already a child of "list_parted2".
+--
+-- DETACH PARTITION
+--
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR:  relation "part_1" is not a partition of relation "list_parted2"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+DROP TABLE part_3_4;
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR:  cannot drop inherited constraint "check_a2" of relation "part_2"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR:  cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR:  cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR:  cannot change inheritance of a partition
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part_2
+drop cascades to table part_5
+drop cascades to table part_5_a
+drop cascades to table part_1
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 02e0720..c3afca6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -439,3 +439,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partition key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR:  TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR:  cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create a permanent relation as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR:  partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ec61b02..c4ed693 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1907,3 +1907,265 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+--
+-- ATTACH PARTITION
+--
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check any TEMP-ness
+CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE perm_part (a int);
+ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
+DROP TABLE temp_parted, perm_part;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+DROP TABLE part_3_4;
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ALTER TABLE part_2 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ALTER TABLE part_2 INHERIT inh_test;
+
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2af3214..7818bc0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1

0004-psql-and-pg_dump-support-for-partitions-19.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-19.patchDownload
From f2a78f2a232a7c7edee0d5c63ef81ef38263b3ae Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/7] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   82 +++++++++++++++++++
 src/bin/pg_dump/common.c                   |   86 ++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  |  118 ++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   12 +++
 src/bin/psql/describe.c                    |   85 +++++++++++++++++---
 src/test/regress/expected/create_table.out |   40 ++++++++++
 src/test/regress/sql/create_table.sql      |   12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60fe794..4e2ba19 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8450,6 +8450,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e2a65d..281ab4f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5621,9 +5621,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5650,6 +5657,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14228,6 +14299,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14256,8 +14338,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14286,7 +14371,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14344,15 +14430,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14522,6 +14615,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e9849ef..a7cb00a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -322,6 +322,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -625,6 +636,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bc44ac5..def1d50 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2535,8 +2563,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index c3afca6..b40a18a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -601,6 +601,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7818bc0..69848e3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -565,6 +565,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

0005-Teach-a-few-places-to-use-partition-check-quals-19.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-19.patchDownload
From b0c407ec42e54b09a9ddc53648658a2da0e98c52 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/7] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |    3 +-
 src/backend/executor/execMain.c        |   69 ++++++++-
 src/backend/executor/nodeModifyTable.c |    4 +-
 src/backend/optimizer/util/plancat.c   |   20 +++
 src/include/nodes/execnodes.h          |    4 +
 src/test/regress/expected/inherit.out  |  272 ++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |   85 ++++++++++
 src/test/regress/expected/update.out   |   27 +++
 src/test/regress/sql/inherit.sql       |   52 ++++++
 src/test/regress/sql/insert.sql        |   59 +++++++
 src/test/regress/sql/update.sql        |   21 +++
 11 files changed, 610 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9773272..c7a6347 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1692,6 +1695,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1746,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1736,7 +1779,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1760,6 +1803,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29d5f57..6eccfb7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index bb16c59..72272d9 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1140,6 +1141,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1225,6 +1227,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828..38ea8e8 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,275 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_ab
+   ->  Seq Scan on part_21_30_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30
+drop cascades to table part_21_30_ab
+drop cascades to table part_21_30_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..2e78fd9 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,88 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, (b+0));
+create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part1 values ('a', 11);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part1 values ('b', 1);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part1 values ('a', 1);
+-- fail
+insert into part4 values ('b', 21);
+ERROR:  new row for relation "part4" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part4 values ('a', 10);
+ERROR:  new row for relation "part4" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part4 values ('b', 10);
+-- fail (partition key a has a NOT NULL constraint)
+insert into part1 values (null);
+ERROR:  null value in column "a" violates not-null constraint
+DETAIL:  Failing row contains (null, null).
+-- fail (expression key (b+0) cannot be null either)
+insert into part1 values (1);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (1, null).
+create table list_parted (
+	a text,
+	b int
+) partition by list (lower(a));
+create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
+create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_aa_bb values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_aa_bb values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+insert into part_aa_bb values (null);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, null).
+-- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- fail
+insert into part_ee_ff1 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
+insert into part_ee_ff1 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_ee_ff1 values ('ff', 1);
+insert into part_ee_ff2 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part4
+drop table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff1
+drop cascades to table part_ee_ff2
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 609899e..a1e9255 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -198,3 +198,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..e22a14e 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,55 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..eb92364 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, (b+0));
+create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part1 values ('a', 11);
+insert into part1 values ('b', 1);
+-- ok
+insert into part1 values ('a', 1);
+-- fail
+insert into part4 values ('b', 21);
+insert into part4 values ('a', 10);
+-- ok
+insert into part4 values ('b', 10);
+
+-- fail (partition key a has a NOT NULL constraint)
+insert into part1 values (null);
+-- fail (expression key (b+0) cannot be null either)
+insert into part1 values (1);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (lower(a));
+create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
+create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_aa_bb values ('cc', 1);
+insert into part_aa_bb values ('AAa', 1);
+insert into part_aa_bb values (null);
+-- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+
+-- fail
+insert into part_ee_ff1 values ('EE', 11);
+-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
+insert into part_ee_ff1 values ('cc', 1);
+-- ok
+insert into part_ee_ff1 values ('ff', 1);
+insert into part_ee_ff2 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index ad58273..d7721ed 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -106,3 +106,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
1.7.1

0006-Tuple-routing-for-partitioned-tables-19.patchtext/x-diff; name=0006-Tuple-routing-for-partitioned-tables-19.patchDownload
From f8f1b9ae04fda26d0b441c0d59a14deb9e2c8576 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/7] Tuple routing for partitioned tables.

Both COPY FROM and INSERT are covered by this commit.  Routing to foreing
partitions is not supported at the moment.

To implement tuple-routing, introduce a PartitionDispatch data structure.
Each partitioned table in a partition tree gets one and contains info
such as a pointer to its partition descriptor, partition key execution
state, global sequence numbers of its leaf partitions and a way to link
to the PartitionDispatch objects of any of its partitions that are
partitioned themselves. Starting with the PartitionDispatch object of the
root partitioned table and a tuple to route, one can get the global
sequence number of the leaf partition that the tuple gets routed to,
if one exists.
---
 src/backend/catalog/partition.c        |  342 +++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c            |  154 ++++++++++++++-
 src/backend/commands/tablecmds.c       |    1 +
 src/backend/executor/execMain.c        |   58 ++++++-
 src/backend/executor/nodeModifyTable.c |  130 ++++++++++++
 src/backend/parser/analyze.c           |    8 +
 src/include/catalog/partition.h        |   11 +
 src/include/executor/executor.h        |    6 +
 src/include/nodes/execnodes.h          |    8 +
 src/test/regress/expected/insert.out   |   55 +++++
 src/test/regress/sql/insert.sql        |   27 +++
 11 files changed, 794 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f439c43..83dc151 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,28 @@ typedef struct PartitionRangeBound
 	bool	lower;		/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
+/*-----------------------
+ * PartitionDispatch - information about one partitioned table in a partition
+ * hiearchy required to route a tuple to one of its partitions
+ *
+ *	relid		OID of the table
+ *	key			Partition key information of the table
+ *	keystate	Execution state required for expressions in the partition key
+ *	partdesc	Partition descriptor of the table
+ *	indexes		Array with partdesc->nparts members (for details on what
+ *				individual members represent, see how they are set in
+ *				RelationGetPartitionDispatchInfo())
+ *-----------------------
+ */
+typedef struct PartitionDispatchData
+{
+	Oid						relid;
+	PartitionKey			key;
+	List				   *keystate;	/* list of ExprState */
+	PartitionDesc			partdesc;
+	int					   *indexes;
+} PartitionDispatchData;
+
 static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
 
@@ -126,12 +148,22 @@ static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, Li
 static int32 partition_rbound_cmp(PartitionKey key,
 					 Datum *datums1, RangeDatumContent *content1, bool lower1,
 					 PartitionRangeBound *b2);
+static int32 partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums);
 
 static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 					int offset, void *probe, bool probe_is_bound);
 static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+/* Support get_partition_for_tuple() */
+static void FormPartitionKeyDatum(PartitionDispatch pd,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -895,6 +927,115 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/* Turn an array of OIDs with N elements into a list */
+#define OID_ARRAY_TO_LIST(arr, N, list) \
+	do\
+	{\
+		int		i;\
+		for (i = 0; i < (N); i++)\
+			(list) = lappend_oid((list), (arr)[i]);\
+	} while(0)
+
+/*
+ * RelationGetPartitionDispatchInfo
+ *		Returns information necessary to route tuples down a partition tree
+ *
+ * All the partitions will be locked with lockmode, unless it is NoLock.
+ * A list of the OIDs of all the leaf partition of rel is returned in
+ * *leaf_part_oids.
+ */
+PartitionDispatch *
+RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids)
+{
+	PartitionDesc	rootpartdesc = RelationGetPartitionDesc(rel);
+	PartitionDispatchData **pd;
+	List	   *all_parts = NIL,
+			   *parted_rels = NIL;
+	ListCell   *lc;
+	int			i,
+				k,
+				num_parted;
+
+	/*
+	 * Lock partitions and collect OIDs of the partitioned ones to prepare
+	 * their PartitionDispatch objects.
+	 *
+	 * Cannot use find_all_inheritors() here, because then the order of OIDs
+	 * in parted_rels list would be unknown, which does not help because down
+	 * below, we assign indexes within individual PartitionDispatch in an
+	 * order that's predetermined (determined by the order of OIDs in
+	 * individual partition descriptors).
+	 */
+	parted_rels = lappend_oid(parted_rels, RelationGetRelid(rel));
+	num_parted = 1;
+	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
+	foreach(lc, all_parts)
+	{
+		Relation		partrel = heap_open(lfirst_oid(lc), lockmode);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+
+		/*
+		 * If this partition is a partitined table, add its children to to the
+		 * end of the list, so that they are processed as well.
+		 */
+		if (partdesc)
+		{
+			num_parted++;
+			parted_rels = lappend_oid(parted_rels, lfirst_oid(lc));
+			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+		}
+
+		heap_close(partrel, NoLock);
+	}
+
+	/* Generate PartitionDispatch objects for all partitioned tables */
+	pd = (PartitionDispatchData **) palloc(num_parted *
+										sizeof(PartitionDispatchData *));
+	*leaf_part_oids = NIL;
+	i = k = 0;
+	foreach(lc, parted_rels)
+	{
+		/* We locked all partitions above */
+		Relation	partrel = heap_open(lfirst_oid(lc), NoLock);
+		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
+		int			j,
+					m;
+
+		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+		pd[i]->relid = RelationGetRelid(partrel);
+		pd[i]->key = RelationGetPartitionKey(partrel);
+		pd[i]->keystate = NIL;
+		pd[i]->partdesc = partdesc;
+		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
+		heap_close(partrel, NoLock);
+
+		m = 0;
+		for (j = 0; j < partdesc->nparts; j++)
+		{
+			Oid		partrelid = partdesc->oids[j];
+
+			if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
+			{
+				*leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
+				pd[i]->indexes[j] = k++;
+			}
+			else
+			{
+				/*
+				 * We can assign indexes this way because of the way
+				 * parted_rels has been generated.
+				 */
+				pd[i]->indexes[j] = -(i + 1 + m);
+				m++;
+			}
+		}
+		i++;
+	}
+
+	return pd;
+}
+
 /* Module-local functions */
 
 /*
@@ -1346,6 +1487,172 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionDispatch pd,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pd->key->partexprs != NIL && pd->keystate == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
+												estate);
+	}
+
+	partexpr_item = list_head(pd->keystate);
+	for (i = 0; i < pd->key->partnatts; i++)
+	{
+		AttrNumber	keycol = pd->key->partattrs[i];
+		Datum		datum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			datum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			datum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = datum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Finds a leaf partition for tuple contained in *slot
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionDispatch *pd,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionDispatch parent;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		cur_offset,
+			cur_index;
+	int		i;
+
+	/* start with the root partitioned table */
+	parent = pd[0];
+	while(true)
+	{
+		PartitionKey	key = parent->key;
+		PartitionDesc	partdesc = parent->partdesc;
+
+		/* Quick exit */
+		if (partdesc->nparts == 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+
+		/* Extract partition key from tuple */
+		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
+
+		if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			/* Disallow nulls in the range partition key of the tuple */
+			for (i = 0; i < key->partnatts; i++)
+				if (isnull[i])
+					ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key of row contains null")));
+		}
+
+		if (partdesc->boundinfo->has_null && isnull[0])
+			/* Tuple maps to the null-accepting list partition */
+			cur_index = partdesc->boundinfo->null_index;
+		else
+		{
+			/* Else bsearch in partdesc->boundinfo */
+			bool	equal = false;
+
+			cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
+												 values, false, &equal);
+			switch (key->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+					if (cur_offset >= 0 && equal)
+						cur_index = partdesc->boundinfo->indexes[cur_offset];
+					else
+						cur_index = -1;
+					break;
+
+				case PARTITION_STRATEGY_RANGE:
+					/*
+					 * Offset returned is such that the bound at offset is
+					 * found to be less or equal with the tuple. So, the
+					 * bound at offset+1 would be the upper bound.
+					 */
+					cur_index = partdesc->boundinfo->indexes[cur_offset+1];
+					break;
+			}
+		}
+
+		/*
+		 * cur_index < 0 means we failed to find a partition of this parent.
+		 * cur_index >= 0 means we either found the leaf partition, or the
+		 * next parent to find a partition of.
+		 */
+		if (cur_index < 0)
+		{
+			*failed_at = parent->relid;
+			return -1;
+		}
+		else if (parent->indexes[cur_index] < 0)
+			parent = pd[-parent->indexes[cur_index]];
+		else
+			break;
+	}
+
+	return parent->indexes[cur_index];
+}
+
 /*
  * qsort_partition_list_value_cmp
  *
@@ -1478,6 +1785,36 @@ partition_rbound_cmp(PartitionKey key,
 }
 
 /*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in rb_datums, rb_content, and
+ * rb_lower) <=, =, >= partition key of tuple (tuple_datums)
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums)
+{
+	int		i;
+	int32	cmpval;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (rb_content[i] != RANGE_DATUM_FINITE)
+			return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
  * partition_bound_cmp
  * 
  * Return whether the bound at offset in boundinfo is <=, =, >= the argument
@@ -1516,7 +1853,10 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 											  bound_datums, content, lower,
 											  (PartitionRangeBound *) probe);
 			}
-
+			else
+				cmpval = partition_rbound_datum_cmp(key,
+													bound_datums, content,
+													(Datum *) probe);
 			break;
 		}
 	}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..3f64f3a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionDispatch	   *partition_dispatch_info;
+	int						num_partitions;
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1401,67 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDispatch *pd;
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i,
+							num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+
+			/* Get the tuple-routing information and lock partitions */
+			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+												  &leaf_parts);
+			num_leaf_parts = list_length(leaf_parts);
+			cstate->partition_dispatch_info = pd;
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
+														sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	partrel;
+
+				/*
+				 * All partitions locked above; will be closed after CopyFrom is
+				 * finished.
+				 */
+				partrel = heap_open(lfirst_oid(cell), NoLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(partrel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  partrel,
+								  1,		/* dummy */
+								  false,	/* no need for partition check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(partrel),
+									gettext_noop("could not convert row type"));
+				leaf_part_rri++;
+				i++;
+			}
+		}
 	}
 	else
 	{
@@ -2255,6 +2320,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2347,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2458,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2486,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_dispatch_info != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2442,7 +2511,11 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2567,59 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_dispatch_info)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+											cstate->partition_dispatch_info,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/* We do not yet have a way to insert into a foreign partition */
+			if (resultRelInfo->ri_FdwRoutine)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot route inserted tuples to a foreign table")));
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+			{
+				tuple = do_convert_tuple(tuple, map);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+			}
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2553,7 +2679,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2704,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2723,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2748,20 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/* Close all partitions and indices thereof */
+	if (cstate->partition_dispatch_info)
+	{
+		int		i;
+
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 71ca739..abfb46b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1322,6 +1322,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c7a6347..54fb771 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2990,3 +2995,52 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6eccfb7..ceb5d44 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_dispatch_info)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+										mtstate->mt_partition_dispatch_info,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* We do not yet have a way to insert into a foreign partition */
+		if (resultRelInfo->ri_FdwRoutine)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot route inserted tuples to a foreign table")));
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1713,69 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDispatch  *pd;
+		int					i,
+							j,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+											  &leaf_parts);
+		mtstate->mt_partition_dispatch_info = pd;
+		num_leaf_parts = list_length(leaf_parts);
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			ftoid = lfirst_oid(cell);
+			Relation	part_rel;
+
+			part_rel = heap_open(ftoid, RowExclusiveLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(part_rel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  part_rel,
+							  1,		/* dummy */
+							  false,	/* no need for partition checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+				operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(part_rel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(part_rel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2093,15 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all partitions and indices thereof */
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 36f8c54..88380ba 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 70d8325..f76c5d9 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -36,6 +38,7 @@ typedef struct PartitionDescData
 } PartitionDescData;
 
 typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionDispatchData *PartitionDispatch;
 
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +48,12 @@ extern void check_new_partition_bound(char *relname, Relation parent, Node *boun
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 List **leaf_part_oids);
+extern int get_partition_for_tuple(PartitionDispatch *pd,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276b..b4d09f9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionDispatch *pd,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ff8b66b..606cb21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,13 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionDispatchData **mt_partition_dispatch_info;
+										/* Tuple-routing support info */
+	int				mt_num_partitions;	/* Number of members in the
+										 * following arrays */
+	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
+	TupleConversionMap **mt_partition_tupconv_maps;
+									/* Per partition tuple conversion map */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 2e78fd9..561cefa 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -230,6 +230,61 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+-- fail (partition key (b+0) is null)
+insert into range_parted values ('a');
+ERROR:  range partition key of row contains null
+select tableoid::regclass, * from range_parted;
+ tableoid | a | b  
+----------+---+----
+ part1    | a |  1
+ part1    | a |  1
+ part2    | a | 10
+ part3    | b |  1
+ part4    | b | 10
+ part4    | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_ee_ff not found in both cases)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_ee_ff values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_ee_ff values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+  tableoid   | a  | b  
+-------------+----+----
+ part_aa_bb  | aA |   
+ part_cc_dd  | cC |  1
+ part_null   |    |  0
+ part_null   |    |  1
+ part_ee_ff1 | ff |  1
+ part_ee_ff1 | EE |  1
+ part_ee_ff2 | ff | 11
+ part_ee_ff2 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index eb92364..846bb58 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,33 @@ insert into part_ee_ff1 values ('cc', 1);
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+-- fail (partition key (b+0) is null)
+insert into range_parted values ('a');
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_ee_ff not found in both cases)
+insert into list_parted values ('EE', 0);
+insert into part_ee_ff values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_ee_ff values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
1.7.1

0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-19.patchtext/x-diff; name=0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-19.patchDownload
From b197e42ee11b043735e05d0c932bd0f670bf8273 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 7/7] Update DDL Partitioning chapter to reflect new developments.

---
 doc/src/sgml/ddl.sgml |  402 ++++++++++---------------------------------------
 1 files changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2793,12 +2793,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2842,59 +2845,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2911,8 +2877,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2940,7 +2908,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -2971,12 +2939,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3024,110 +2965,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3145,22 +2995,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3397,9 +3173,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3423,18 +3199,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
1.7.1

#160Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#156)
Re: Declarative partitioning - another take

On Fri, Nov 25, 2016 at 5:49 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/11/25 11:44, Robert Haas wrote:

On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:

Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).

That's going to have to be fixed somehow. How bad would it be if we
passed ExecQual's third argument as false for partition constraints?
Or else you could generate the actual constraint as expr IS NOT NULL
AND expr >= lb AND expr < ub.

About the former, I think that might work. If a column is NULL, it would
be caught in ExecConstraints() even before ExecQual() is called, because
of the NOT NULL constraint. If an expression is NULL, or for some reason,
the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
column or expression, then ExecQual() would fail if we passed false for
resultForNull. Not sure if that would be violating the SQL specification
though.

I don't think the SQL specification can have anything to say about an
implicit constraint generated as an implementation detail of our
partitioning implementation.

The latter would work too. But I guess we would only emit expr IS NOT
NULL, not column IS NOT NULL, because columns are covered by NOT NULL
constraints.

Right.

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

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

#161Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#158)
Re: Declarative partitioning - another take

On Tue, Nov 29, 2016 at 6:24 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

# All times in seconds (on my modestly-powerful development VM)
#
# nrows = 10,000,000 generated using:
#
# INSERT INTO $tab
# SELECT '$last'::date - ((s.id % $maxsecs + 1)::bigint || 's')::interval,
# (random() * 5000)::int % 4999 + 1,
# case s.id % 10
# when 0 then 'a'
# when 1 then 'b'
# when 2 then 'c'
# ...
# when 9 then 'j'
# end
# FROM generate_series(1, $nrows) s(id)
# ORDER BY random();
#
# The first item in the select list is basically a date that won't fall
# outside the defined partitions.

Time for a plain table = 98.1 sec

#part parted tg-direct-map tg-if-else
===== ====== ============= ==========
10 114.3 1483.3 742.4
50 112.5 1476.6 2016.8
100 117.1 1498.4 5386.1
500 125.3 1475.5 --
1000 129.9 1474.4 --
5000 137.5 1491.4 --
10000 154.7 1480.9 --

Very nice!

Obviously, it would be nice if the overhead were even lower, but it's
clearly a vast improvement over what we have today.

Regarding tuple-mapping-required vs no-tuple-mapping-required, all cases
currently require tuple-mapping, because the decision is based on the
result of comparing parent and partition TupleDesc using
equalTupleDescs(), which fails so quickly because TupleDesc.tdtypeid are
not the same. Anyway, I simply commented out the tuple-mapping statement
in ExecInsert() to observe just slightly improved numbers as follows
(comparing with numbers in the table just above):

#part (sub-)parted
===== =================
10 113.9 (vs. 127.0)
100 135.7 (vs. 156.6)
500 182.1 (vs. 191.8)

I think you should definitely try to get that additional speedup when
you can. It doesn't seem like a lot when you think of how much is
already being saved, but a healthy number of users are going to
compare it to the performance on an unpartitioned table rather than to
our historical performance. 127/98.1 = 1.29, but 113.9/98.1 = 1.16
-- and obviously a 16% overhead from partitioning is way better than a
29% overhead, even if the old overhead was a million percent.

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

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

#162Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#160)
Re: Declarative partitioning - another take

On Thu, Dec 1, 2016 at 12:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Nov 25, 2016 at 5:49 AM, Amit Langote wrote:

On 2016/11/25 11:44, Robert Haas wrote:

On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:

Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).

That's going to have to be fixed somehow. How bad would it be if we
passed ExecQual's third argument as false for partition constraints?
Or else you could generate the actual constraint as expr IS NOT NULL
AND expr >= lb AND expr < ub.

About the former, I think that might work. If a column is NULL, it would
be caught in ExecConstraints() even before ExecQual() is called, because
of the NOT NULL constraint. If an expression is NULL, or for some reason,
the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
column or expression, then ExecQual() would fail if we passed false for
resultForNull. Not sure if that would be violating the SQL specification
though.

I don't think the SQL specification can have anything to say about an
implicit constraint generated as an implementation detail of our
partitioning implementation.

Yeah, I thought so too.

The latter would work too. But I guess we would only emit expr IS NOT
NULL, not column IS NOT NULL, because columns are covered by NOT NULL
constraints.

Right.

The latest patch I posted earlier today has this implementation.

Thanks,
Amit

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

#163Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#162)
1 attachment(s)
Re: Declarative partitioning - another take

On Wed, Nov 30, 2016 at 10:56 AM, Amit Langote <amitlangote09@gmail.com> wrote:

The latest patch I posted earlier today has this implementation.

I decided to try out these patches today with #define
CLOBBER_CACHE_ALWAYS 1 in pg_config_manual.h, which found a couple of
problems:

1. RelationClearRelation() wasn't preserving the rd_partkey, even
though there's plenty of code that relies on it not changing while we
hold a lock on the relation - in particular, transformPartitionBound.

2. partition_bounds_equal() was using the comparator and collation for
partitioning column 0 to compare the datums for all partitioning
columns. It's amazing this passed the regression tests.

The attached incremental patch fixes those things and some cosmetic
issues I found along the way.

3. RelationGetPartitionDispatchInfo() is badly broken:

1010 pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
1011 pd[i]->relid = RelationGetRelid(partrel);
1012 pd[i]->key = RelationGetPartitionKey(partrel);
1013 pd[i]->keystate = NIL;
1014 pd[i]->partdesc = partdesc;
1015 pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
1016 heap_close(partrel, NoLock);
1017
1018 m = 0;
1019 for (j = 0; j < partdesc->nparts; j++)
1020 {
1021 Oid partrelid = partdesc->oids[j];

This code imagines that pointers it extracted from partrel are certain
to remain valid after heap_close(partrel, NoLock), perhaps on the
strength of the fact that we still retain a lock on the relation. But
this isn't the case. As soon as nobody has the relation open, a call
to RelationClearRelation() will destroy the relcache entry and
everything to which it points; with CLOBBER_CACHE_ALWAYS, I see a
failure at line 1021:

#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
1021 Oid partrelid = partdesc->oids[j];
(gdb) bt 5
#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
#1 0x0000000109b8d71f in ExecInitModifyTable (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at nodeModifyTable.c:1730
#2 0x0000000109b5e7ac in ExecInitNode (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at execProcnode.c:159
#3 0x0000000109b58548 in InitPlan (queryDesc=0x7fd12b87b638,
eflags=0) at execMain.c:961
#4 0x0000000109b57dcd in standard_ExecutorStart
(queryDesc=0x7fd12b87b638, eflags=0) at execMain.c:239
(More stack frames follow...)
Current language: auto; currently minimal
(gdb) p debug_query_string
$1 = 0x7fd12b84c238 "insert into list_parted values (null, 1);"
(gdb) p partdesc[0]
$2 = {
nparts = 2139062143,
oids = 0x7f7f7f7f7f7f7f7f,
boundinfo = 0x7f7f7f7f7f7f7f7f
}

As you can see, the partdesc is no longer valid here. I'm not
immediately sure how to fix this; this isn't a simple thinko. You
need to keep the relations open for the whole duration of the query,
not just long enough to build the dispatch info. I think you should
try to revise this so that each relation is opened once and kept open;
maybe the first loop should be making a pointer-list of Relations
rather than an int-list of relation OIDs. And it seems to me (though
I'm getting a little fuzzy here because it's late) that you need all
of the partitions open, not just the ones that are subpartitioned,
because how else are you going to know how to remap the tuple if the
column order is different? But I don't see this code doing that,
which makes me wonder if the partitions are being opened yet again in
some other location.

I recommend that once you fix this, you run 'make check' with #define
CLOBBER_CACHE_ALWAYS 1 and look for other hazards. Such mistakes are
easy to make with this kind of patch.

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

Attachments:

partition-fixes-rmh.patchtext/x-patch; charset=US-ASCII; name=partition-fixes-rmh.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 83dc151..cc9009d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -53,16 +53,16 @@
  * Information about bounds of a partitioned relation
  *
  * A list partition datum that is known to be NULL is never put into the
- * datums array, instead it is tracked using has_null and null_index fields.
+ * datums array. Instead, it is tracked using has_null and null_index fields.
  *
- * In case of range partitioning, ndatums is far less than 2 * nparts, because
- * a partition's upper bound and the next partition's lower bound are same
- * in most common cases, and we only store one of them.
+ * In the case of range partitioning, ndatums will typically be far less than
+ * 2 * nparts, because a partition's upper bound and the next partition's lower
+ * bound are the same in most common cases, and we only store one of them.
  *
- * In case of list partitioning, the indexes array stores one entry for every
- * datum, which is the index of the partition that accepts a given datum.
- * Wheareas, in case of range partitioning, it stores one entry per distinct
- * range datum, which is the index of the partition of which a given datum
+ * In the case of list partitioning, the indexes array stores one entry for
+ * every datum, which is the index of the partition that accepts a given datum.
+ * In case of range partitioning, it stores one entry per distinct range
+ * datum, which is the index of the partition for which a given datum
  * is an upper bound.
  */
 
@@ -135,16 +135,19 @@ typedef struct PartitionDispatchData
 	int					   *indexes;
 } PartitionDispatchData;
 
-static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
-static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+							   void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+						   void *arg);
 
 static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
-static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
-					   bool *need_relabel);
+static Oid get_partition_operator(PartitionKey key, int col,
+					   StrategyNumber strategy, bool *need_relabel);
 static List *generate_partition_qual(Relation rel, bool recurse);
 
-static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+					 List *datums, bool lower);
 static int32 partition_rbound_cmp(PartitionKey key,
 					 Datum *datums1, RangeDatumContent *content1, bool lower1,
 					 PartitionRangeBound *b2);
@@ -152,9 +155,11 @@ static int32 partition_rbound_datum_cmp(PartitionKey key,
 						   Datum *rb_datums, RangeDatumContent *rb_content,
 						   Datum *tuple_datums);
 
-static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+static int32 partition_bound_cmp(PartitionKey key,
+					PartitionBoundInfo boundinfo,
 					int offset, void *probe, bool probe_is_bound);
-static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+static int partition_bound_bsearch(PartitionKey key,
+						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
 /* Support get_partition_for_tuple() */
@@ -636,8 +641,8 @@ partition_bounds_equal(PartitionKey key,
 		{
 			int32	cmpval;
 
-			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
-													 key->partcollation[0],
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[j],
+													 key->partcollation[j],
 													 b1->datums[i][j],
 													 b2->datums[i][j]));
 			if (cmpval != 0)
@@ -976,7 +981,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
 
 		/*
-		 * If this partition is a partitined table, add its children to to the
+		 * If this partition is a partitioned table, add its children to the
 		 * end of the list, so that they are processed as well.
 		 */
 		if (partdesc)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index abfb46b..c77b216 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1990,9 +1990,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		pfree(newattno);
 
 		/*
-		 * Close the parent rel, but keep our ShareUpdateExclusiveLock on it
-		 * until xact commit.  That will prevent someone else from deleting or
-		 * ALTERing the parent before the child is committed.
+		 * Close the parent rel, but keep our lock on it until xact commit.
+		 * That will prevent someone else from deleting or ALTERing the parent
+		 * before the child is committed.
 		 */
 		heap_close(relation, NoLock);
 	}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c958092..2da7ae3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1192,7 +1192,7 @@ equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
 
 		/*
 		 * Now compare partition bound collections.  The logic to iterate over
-		 * the collections is local to partition.c.
+		 * the collections is private to partition.c.
 		 */
 		if (partdesc1->boundinfo != NULL)
 		{
@@ -2604,7 +2604,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-
+		/* partition key must be preserved */
+		SWAPFIELD(PartitionKey, rd_partkey);
+		SWAPFIELD(MemoryContext, rd_partkeycxt);
 		/* preserve old partdesc if no logical change */
 		if (keep_partdesc)
 		{
#164Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#163)
7 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/07 13:38, Robert Haas wrote:

On Wed, Nov 30, 2016 at 10:56 AM, Amit Langote <amitlangote09@gmail.com> wrote:

The latest patch I posted earlier today has this implementation.

I decided to try out these patches today with #define
CLOBBER_CACHE_ALWAYS 1 in pg_config_manual.h, which found a couple of
problems:

1. RelationClearRelation() wasn't preserving the rd_partkey, even
though there's plenty of code that relies on it not changing while we
hold a lock on the relation - in particular, transformPartitionBound.

Oh, I thought an AccessExclusiveLock on the relation would prevent having
to worry about that, but guess I'm wrong. Perhaps, having a lock on a
table does not preclude RelationClearRelation() resetting the table's
relcache.

2. partition_bounds_equal() was using the comparator and collation for
partitioning column 0 to compare the datums for all partitioning
columns. It's amazing this passed the regression tests.

Oops, it seems that the regression tests where the above code might be
exercised consisted only of range partition key with columns all of the
same type: create table test(a int, b int) partition by range (a, (a+b));

The attached incremental patch fixes those things and some cosmetic
issues I found along the way.

Thanks for fixing these. Attached patches include these changes.

3. RelationGetPartitionDispatchInfo() is badly broken:

1010 pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
1011 pd[i]->relid = RelationGetRelid(partrel);
1012 pd[i]->key = RelationGetPartitionKey(partrel);
1013 pd[i]->keystate = NIL;
1014 pd[i]->partdesc = partdesc;
1015 pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
1016 heap_close(partrel, NoLock);
1017
1018 m = 0;
1019 for (j = 0; j < partdesc->nparts; j++)
1020 {
1021 Oid partrelid = partdesc->oids[j];

This code imagines that pointers it extracted from partrel are certain
to remain valid after heap_close(partrel, NoLock), perhaps on the
strength of the fact that we still retain a lock on the relation. But
this isn't the case. As soon as nobody has the relation open, a call
to RelationClearRelation() will destroy the relcache entry and
everything to which it points; with CLOBBER_CACHE_ALWAYS, I see a
failure at line 1021:

#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
1021 Oid partrelid = partdesc->oids[j];
(gdb) bt 5
#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
#1 0x0000000109b8d71f in ExecInitModifyTable (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at nodeModifyTable.c:1730
#2 0x0000000109b5e7ac in ExecInitNode (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at execProcnode.c:159
#3 0x0000000109b58548 in InitPlan (queryDesc=0x7fd12b87b638,
eflags=0) at execMain.c:961
#4 0x0000000109b57dcd in standard_ExecutorStart
(queryDesc=0x7fd12b87b638, eflags=0) at execMain.c:239
(More stack frames follow...)
Current language: auto; currently minimal
(gdb) p debug_query_string
$1 = 0x7fd12b84c238 "insert into list_parted values (null, 1);"
(gdb) p partdesc[0]
$2 = {
nparts = 2139062143,
oids = 0x7f7f7f7f7f7f7f7f,
boundinfo = 0x7f7f7f7f7f7f7f7f
}

As you can see, the partdesc is no longer valid here. I'm not
immediately sure how to fix this; this isn't a simple thinko. You
need to keep the relations open for the whole duration of the query,
not just long enough to build the dispatch info. I think you should
try to revise this so that each relation is opened once and kept open;
maybe the first loop should be making a pointer-list of Relations
rather than an int-list of relation OIDs.

Thanks for the explanation, I see the problem. I changed
PartitionDispatchData such that its 1st field is now Relation (instead of
current Oid), where we keep the relation descriptor of the corresponding
partitioned table. Currently, only the leaf relations are held open in
their respective ResultRelInfo's (ModifyTableState.mt_partitions) and
later closed in ExecEndModifyTable(). Similarly, we have the
ModifyTableState.mt_partition_dispatch_info array, each of whose members
holds open a partitioned relation using the new field. We close that too
along with leaf partitions as just mentioned. Similarly in case of COPY FROM.

And it seems to me (though
I'm getting a little fuzzy here because it's late) that you need all
of the partitions open, not just the ones that are subpartitioned,
because how else are you going to know how to remap the tuple if the
column order is different? But I don't see this code doing that,
which makes me wonder if the partitions are being opened yet again in
some other location.

Actually leaf partitions are opened by the respective callers of
RelationGetPartitionDispatchInfo() and held open in the corresponding
ResultRelInfo's (ModifyTableState.mt_partitions). As mentioned above,
they will be closed by either ExecEndModifyTable() or CopyFrom() after
finishing the INSERT or COPY, respectively.

I recommend that once you fix this, you run 'make check' with #define
CLOBBER_CACHE_ALWAYS 1 and look for other hazards. Such mistakes are
easy to make with this kind of patch.

With the attached latest version of the patches, I couldn't see any
failures with a CLOBBER_CACHE_ALWAYS build.

Thanks,
Amit

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables-20.patchtext/x-diff; name=0001-Catalog-and-DDL-for-partitioned-tables-20.patchDownload
From de77cca87dd975df5d7897b25ca5b4d0540acd47 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/7] Catalog and DDL for partitioned tables.

In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.

PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
 doc/src/sgml/catalogs.sgml                 | 112 ++++++-
 doc/src/sgml/ref/create_table.sgml         |  62 ++++
 src/backend/access/common/reloptions.c     |   2 +
 src/backend/catalog/Makefile               |   2 +-
 src/backend/catalog/aclchk.c               |   2 +
 src/backend/catalog/dependency.c           |  10 +-
 src/backend/catalog/heap.c                 | 165 +++++++++-
 src/backend/catalog/index.c                |   4 +-
 src/backend/catalog/objectaddress.c        |   5 +-
 src/backend/catalog/pg_constraint.c        |   2 +-
 src/backend/commands/analyze.c             |   6 +-
 src/backend/commands/copy.c                |   6 +
 src/backend/commands/indexcmds.c           |  24 +-
 src/backend/commands/lockcmds.c            |   2 +-
 src/backend/commands/policy.c              |   5 +-
 src/backend/commands/seclabel.c            |   3 +-
 src/backend/commands/sequence.c            |   3 +-
 src/backend/commands/tablecmds.c           | 509 ++++++++++++++++++++++++++++-
 src/backend/commands/trigger.c             |  16 +-
 src/backend/commands/vacuum.c              |   3 +-
 src/backend/executor/execMain.c            |   2 +
 src/backend/executor/nodeModifyTable.c     |   3 +-
 src/backend/nodes/copyfuncs.c              |  34 ++
 src/backend/nodes/equalfuncs.c             |  29 ++
 src/backend/nodes/outfuncs.c               |  28 ++
 src/backend/parser/gram.y                  | 105 +++++-
 src/backend/parser/parse_agg.c             |  10 +
 src/backend/parser/parse_expr.c            |   5 +
 src/backend/parser/parse_func.c            |   3 +
 src/backend/parser/parse_utilcmd.c         |  73 ++++-
 src/backend/rewrite/rewriteDefine.c        |   3 +-
 src/backend/rewrite/rewriteHandler.c       |   3 +-
 src/backend/rewrite/rowsecurity.c          |   3 +-
 src/backend/utils/cache/relcache.c         | 265 ++++++++++++++-
 src/backend/utils/cache/syscache.c         |  12 +
 src/include/catalog/dependency.h           |   3 +-
 src/include/catalog/heap.h                 |  10 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |   1 +
 src/include/catalog/pg_partitioned_table.h |  76 +++++
 src/include/commands/defrem.h              |   2 +
 src/include/nodes/nodes.h                  |   2 +
 src/include/nodes/parsenodes.h             |  29 ++
 src/include/parser/parse_node.h            |   3 +-
 src/include/pg_config_manual.h             |   5 +
 src/include/utils/rel.h                    |  68 ++++
 src/include/utils/syscache.h               |   1 +
 src/test/regress/expected/alter_table.out  |  46 +++
 src/test/regress/expected/create_table.out | 168 ++++++++++
 src/test/regress/expected/sanity_check.out |   1 +
 src/test/regress/sql/alter_table.sql       |  32 ++
 src/test/regress/sql/create_table.sql      | 146 +++++++++
 52 files changed, 2047 insertions(+), 70 deletions(-)
 create mode 100644 src/include/catalog/pg_partitioned_table.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c4246dcd86..126cfdfad8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+      <entry>information about partition key of tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -1723,7 +1728,8 @@
       <entry><type>char</type></entry>
       <entry></entry>
       <entry>
-       <literal>r</> = ordinary table, <literal>i</> = index,
+       <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+       <literal>i</> = index
        <literal>S</> = sequence, <literal>v</> = view,
        <literal>m</> = materialized view,
        <literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partitioned-table">
+  <title><structname>pg_partitioned_table</structname></title>
+
+  <indexterm zone="catalog-pg-partitioned-table">
+   <primary>pg_partitioned_table</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partitioned_table</structname> stores
+   information about how tables are partitioned.
+  </para>
+
+  <table>
+   <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partstrat</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>
+       Partitioning strategy; <literal>l</> = list partitioned table,
+       <literal>r</> = range partitioned table
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partnatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of columns in partition key</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partattrs</structfield></entry>
+      <entry><type>int2vector</type></entry>
+      <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+      <entry>
+       This is an array of <structfield>partnatts</structfield> values that
+       indicate which table columns are part of the partition key.  For
+       example, a value of <literal>1 3</literal> would mean that the first
+       and the third table columns make up the partition key.  A zero in this
+       array indicates that the corresponding partition key column is an
+       expression, rather than a simple column reference.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partclass</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       operator class to use.  See
+       <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partcollation</structfield></entry>
+      <entry><type>oidvector</type></entry>
+      <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+      <entry>
+       For each column in the partition key, this contains the OID of the
+       the collation to use for partitioning.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>partexprs</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression trees (in <function>nodeToString()</function>
+       representation) for partition key columns that are not simple column
+       references.  This is a list with one element for each zero
+       entry in <structfield>partattrs</>.  Null if all partition key columns
+       are simple references.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bf2ad64d66..3e747df17e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     [, ... ]
 ] )
 [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
 ) ]
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+    <listitem>
+     <para>
+      The optional <literal>PARTITION BY</literal> clause specifies a strategy
+      of partitioning the table.  The table thus created is called a
+      <firstterm>partitioned</firstterm> table.  The parenthesized list of
+      columns or expressions forms the <firstterm>partition key</firstterm>
+      for the table.  When using range partitioning, the partition key can
+      include multiple columns or expressions, but for list partitioning, the
+      partition key must consist of a single column or expression.  If no
+      btree operator class is specified when creating a partitioned table,
+      the default btree operator class for the datatype will be used.  If
+      there is none, an error will be reported.
+     </para>
+
+     <para>
+      A partitioned table is divided into sub-tables (called partitions),
+      which are created using separate <literal>CREATE TABLE</> commands.
+      The partitioned table is itself empty.  A data row inserted into the
+      table is routed to a partition based on the value of columns or
+      expressions in the partition key.  If no existing partition matches
+      the values in the new row, an error will be reported.
+     </para>
+
+     <para>
+      Partitioned tables do not support <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+      <literal>FOREIGN KEY</literal> constraints; however, you can define
+      these constraints on individual partitions.
+     </para>
+
+     <para>
+      When using range partitioning, a <literal>NOT NULL</literal> constraint
+      is added to each non-expression column in the partition key.
+     </para>
+
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
     <listitem>
      <para>
@@ -1369,6 +1411,26 @@ CREATE TABLE employees OF employee_type (
     salary WITH OPTIONS DEFAULT 1000
 );
 </programlisting></para>
+
+  <para>
+   Create a range partitioned table:
+<programlisting>
+CREATE TABLE measurement (
+    city_id         int not null,
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+</programlisting></para>
+
+  <para>
+   Create a list partitioned table:
+<programlisting>
+CREATE TABLE cities (
+    name         text not null,
+    population   int,
+) PARTITION BY LIST (name);
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 83a97b06ab..34018cac7c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
 		case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
 		default:
 			/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610049..362deca8ee 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -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_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df6710d1..3086021432 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				objects = list_concat(objects, objs);
 				objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
 				objects = list_concat(objects, objs);
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+				objects = list_concat(objects, objs);
 				break;
 			case ACL_OBJECT_SEQUENCE:
 				objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b697e88ef0..0cdd1c5c6c 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1352,7 +1352,8 @@ void
 recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior)
+								DependencyType self_behavior,
+								bool ignore_self)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
@@ -1407,9 +1408,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		context.addrs->numrefs = outrefs;
 
 		/* Record the self-dependencies */
-		recordMultipleDependencies(depender,
-								   self_addrs->refs, self_addrs->numrefs,
-								   self_behavior);
+		if (!ignore_self)
+			recordMultipleDependencies(depender,
+									   self_addrs->refs, self_addrs->numrefs,
+									   self_behavior);
 
 		free_object_addresses(self_addrs);
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0b804e7ac6..596b29ef56 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
@@ -1104,7 +1106,8 @@ heap_create_with_catalog(const char *relname,
 		if (IsBinaryUpgrade &&
 			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
 			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
-			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE ||
+			 relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
 				ereport(ERROR,
@@ -1138,6 +1141,7 @@ heap_create_with_catalog(const char *relname,
 			case RELKIND_VIEW:
 			case RELKIND_MATVIEW:
 			case RELKIND_FOREIGN_TABLE:
+			case RELKIND_PARTITIONED_TABLE:
 				relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
 											  relnamespace);
 				break;
@@ -1182,7 +1186,8 @@ heap_create_with_catalog(const char *relname,
 							  relkind == RELKIND_VIEW ||
 							  relkind == RELKIND_MATVIEW ||
 							  relkind == RELKIND_FOREIGN_TABLE ||
-							  relkind == RELKIND_COMPOSITE_TYPE))
+							  relkind == RELKIND_COMPOSITE_TYPE ||
+							  relkind == RELKIND_PARTITIONED_TABLE))
 		new_array_oid = AssignTypeArrayOid();
 
 	/*
@@ -1349,7 +1354,9 @@ heap_create_with_catalog(const char *relname,
 	if (relpersistence == RELPERSISTENCE_UNLOGGED)
 	{
 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
-			   relkind == RELKIND_TOASTVALUE);
+			   relkind == RELKIND_TOASTVALUE ||
+			   relkind == RELKIND_PARTITIONED_TABLE);
+
 		heap_create_init_fork(new_rel_desc);
 	}
 
@@ -1796,6 +1803,12 @@ heap_drop_with_catalog(Oid relid)
 	}
 
 	/*
+	 * If a partitioned table, delete the pg_partitioned_table tuple.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RemovePartitionKeyByRelId(relid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2028,6 +2041,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 		attNos = NULL;
 
 	/*
+	 * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+	 * constraint makes no sense.
+	 */
+	if (is_no_inherit &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+						 RelationGetRelationName(rel))));
+
+	/*
 	 * Create the Check Constraint
 	 */
 	constrOid =
@@ -3013,3 +3037,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
 	lappend_cell_oid(list, prev, datum);
 	return list;
 }
+
+/*
+ * StorePartitionKey
+ *		Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+				  char strategy,
+				  int16 partnatts,
+				  AttrNumber *partattrs,
+				  List *partexprs,
+				  Oid *partopclass,
+				  Oid *partcollation)
+{
+	int			i;
+	int2vector *partattrs_vec;
+	oidvector  *partopclass_vec;
+	oidvector  *partcollation_vec;
+	Datum		partexprDatum;
+	Relation	pg_partitioned_table;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_partitioned_table];
+	bool		nulls[Natts_pg_partitioned_table];
+	ObjectAddress   myself;
+	ObjectAddress   referenced;
+
+	Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	/* Copy the partition attribute numbers, opclass OIDs into arrays */
+	partattrs_vec = buildint2vector(partattrs, partnatts);
+	partopclass_vec = buildoidvector(partopclass, partnatts);
+	partcollation_vec = buildoidvector(partcollation, partnatts);
+
+	/* Convert the expressions (if any) to a text datum */
+	if (partexprs)
+	{
+		char       *exprString;
+
+		exprString = nodeToString(partexprs);
+		partexprDatum = CStringGetTextDatum(exprString);
+		pfree(exprString);
+	}
+	else
+		partexprDatum = (Datum) 0;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	/* Only this can ever be NULL */
+	if (!partexprDatum)
+		nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partattrs - 1] =  PointerGetDatum(partattrs_vec);
+	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+	values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+	simple_heap_insert(pg_partitioned_table, tuple);
+
+	/* Update the indexes on pg_partitioned_table */
+	CatalogUpdateIndexes(pg_partitioned_table, tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+
+	/* Mark this relation as dependent on a few things as follows */
+	myself.classId = RelationRelationId;
+	myself.objectId = RelationGetRelid(rel);;
+	myself.objectSubId = 0;
+
+	/* Operator class and collation per key column */
+	for (i = 0; i < partnatts; i++)
+	{
+		referenced.classId = OperatorClassRelationId;
+		referenced.objectId = partopclass[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = partcollation[i];
+		referenced.objectSubId = 0;
+
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/*
+	 * Anything mentioned in the expressions.  We must ignore the column
+	 * references, which will depend on the table itself; there is no
+	 * separate partition key object.
+	 */
+	if (partexprs)
+		recordDependencyOnSingleRelExpr(&myself,
+										(Node *) partexprs,
+										RelationGetRelid(rel),
+										DEPENDENCY_NORMAL,
+										DEPENDENCY_AUTO, true);
+
+	/*
+	 * We must invalidate the relcache so that the next
+	 * CommandCounterIncrement() will cause the same to be rebuilt using the
+	 * information in just created catalog entry.
+	 */
+	CacheInvalidateRelcache(rel);
+}
+
+/*
+ *  RemovePartitionKeyByRelId
+ *		Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d8f3..08b0989112 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
 										  (Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+											DEPENDENCY_AUTO, false);
 		}
 	}
 	else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17cdb..bb4b080b00 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
 								RelationGetRelationName(relation))));
 			break;
 		case OBJECT_TABLE:
-			if (relation->rd_rel->relkind != RELKIND_RELATION)
+			if (relation->rd_rel->relkind != RELKIND_RELATION &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfo(buffer, _("table %s"),
 							 relname);
 			break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	switch (relForm->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			appendStringInfoString(buffer, "table");
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe6899f..724b41e64c 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL);
+										DEPENDENCY_NORMAL, false);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb223..f4afcd9aae 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
 	 * locked the relation.
 	 */
 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
-		onerel->rd_rel->relkind == RELKIND_MATVIEW)
+		onerel->rd_rel->relkind == RELKIND_MATVIEW ||
+		onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Regular table, so we'll use the regular row acquisition function */
 		acquirefunc = acquire_sample_rows;
@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 		/* Check table type (MATVIEW can't happen, but might as well allow) */
 		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
-			childrel->rd_rel->relkind == RELKIND_MATVIEW)
+			childrel->rd_rel->relkind == RELKIND_MATVIEW ||
+			childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			/* Regular table, so use the regular row acquisition function */
 			acquirefunc = acquire_sample_rows;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ec5d6f1565..b30c2c788e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot copy from sequence \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot copy from partitioned table \"%s\"",
+							RelationGetRelationName(rel)),
+					 errhint("Try the COPY (SELECT ...) TO variant.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6530..9735bb2cc9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 				  char *accessMethodName, Oid accessMethodId,
 				  bool amcanorder,
 				  bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexName(const char *tabname, Oid namespaceId,
 				List *colnames, List *exclusionOpNames,
 				bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot create index on foreign table \"%s\"",
 							RelationGetRelationName(rel))));
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create index on partitioned table \"%s\"",
+							RelationGetRelationName(rel))));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 		/*
 		 * Identify the opclass to use.
 		 */
-		classOidP[attn] = GetIndexOpClass(attribute->opclass,
-										  atttype,
-										  accessMethodName,
-										  accessMethodId);
+		classOidP[attn] = ResolveOpClass(attribute->opclass,
+										 atttype,
+										 accessMethodName,
+										 accessMethodId);
 
 		/*
 		 * Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
 /*
  * Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
  */
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
-				char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId)
 {
 	char	   *schemaname;
 	char	   *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75977..9e62e00b8d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 								 * check */
 
 	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 70e22c1000..6da3205c9e 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
 						rv->relname)));
 
 	/* Relation type MUST be a table. */
-	if (relkind != RELKIND_RELATION)
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table", rv->relname)));
@@ -384,7 +384,8 @@ RemovePolicyById(Oid policy_id)
 	relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
 
 	rel = heap_open(relid, AccessExclusiveLock);
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e124c1..2b0ae34830 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 				relation->rd_rel->relkind != RELKIND_VIEW &&
 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
 				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+				relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 7e37108b8d..1ab9030a3c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
 
 		/* Must be a regular or foreign table */
 		if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
-			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+			  tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+			  tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("referenced relation \"%s\" is not a table or foreign table",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6322fa75a7..826c47685b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
@@ -252,6 +253,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("foreign table \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not a foreign table"),
 	gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
+	{RELKIND_PARTITIONED_TABLE,
+		ERRCODE_UNDEFINED_TABLE,
+		gettext_noop("table \"%s\" does not exist"),
+		gettext_noop("table \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a table"),
+	gettext_noop("Use DROP TABLE to remove a table.")},
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
 								Oid oldRelOid, void *arg);
 static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
 								 Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation);
 
 
 /* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
 
+	if (stmt->partspec != NULL)
+	{
+		if (relkind != RELKIND_RELATION)
+			elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+		relkind = RELKIND_PARTITIONED_TABLE;
+	}
+
 	/*
 	 * Look up the namespace in which we are supposed to create the relation,
 	 * check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * affect other relkinds, but it would complicate interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
-									   (relkind == RELKIND_RELATION));
+									   (relkind == RELKIND_RELATION ||
+										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
 	/*
@@ -698,6 +718,65 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	rel = relation_open(relationId, AccessExclusiveLock);
 
 	/*
+	 * Process the partitioning specification (if any) and store the
+	 * partition key information into the catalog.
+	 */
+	if (stmt->partspec)
+	{
+		char			strategy;
+		int				partnatts,
+						i;
+		AttrNumber		partattrs[PARTITION_MAX_KEYS];
+		Oid				partopclass[PARTITION_MAX_KEYS];
+		Oid				partcollation[PARTITION_MAX_KEYS];
+		List		   *partexprs = NIL;
+		List		   *cmds = NIL;
+
+		/*
+		 * We need to transform the raw parsetrees corresponding to partition
+		 * expressions into executable expression trees.  Like column defaults
+		 * and CHECK constraints, we could not have done the transformation
+		 * earlier.
+		 */
+		stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+												&strategy);
+		ComputePartitionAttrs(rel, stmt->partspec->partParams,
+							  partattrs, &partexprs, partopclass,
+							  partcollation);
+
+		partnatts = list_length(stmt->partspec->partParams);
+		StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+						  partopclass, partcollation);
+
+		/* Force key columns to be NOT NULL when using range partitioning */
+		if (strategy == PARTITION_STRATEGY_RANGE)
+		{
+			for (i = 0; i < partnatts; i++)
+			{
+				AttrNumber	partattno = partattrs[i];
+				Form_pg_attribute attform = descriptor->attrs[partattno-1];
+
+				if (partattno != 0 && !attform->attnotnull)
+				{
+					/* Add a subcommand to make this one NOT NULL */
+					AlterTableCmd *cmd = makeNode(AlterTableCmd);
+
+					cmd->subtype = AT_SetNotNull;
+					cmd->name = pstrdup(NameStr(attform->attname));
+					cmds = lappend(cmds, cmd);
+				}
+			}
+
+			/*
+			 * Although, there cannot be any partitions yet, we still need to
+			 * pass true for recurse; ATPrepSetNotNull() complains if we don't
+			 */
+			if (cmds != NIL)
+				AlterTableInternal(RelationGetRelid(rel), cmds, true);
+		}
+	}
+
+	/*
 	 * Now add any newly specified column default values and CHECK constraints
 	 * to the new relation.  These are passed to us in the form of raw
 	 * parsetrees; we need to transform them to executable expression trees
@@ -927,6 +1006,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	HeapTuple	tuple;
 	struct DropRelationCallbackState *state;
 	char		relkind;
+	char		expected_relkind;
 	Form_pg_class classform;
 	LOCKMODE	heap_lockmode;
 
@@ -955,7 +1035,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 		return;					/* concurrently dropped, so nothing to do */
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	if (classform->relkind != relkind)
+	/*
+	 * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+	 * but RemoveRelations() can only pass one relkind for a given relation.
+	 * It chooses RELKIND_RELATION for both regular and partitioned tables.
+	 * That means we must be careful before giving the wrong type error when
+	 * the relation is RELKIND_PARTITIONED_TABLE.
+	 */
+	if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+		expected_relkind = RELKIND_RELATION;
+	else
+		expected_relkind = classform->relkind;
+
+	if (relkind != expected_relkind)
 		DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
 
 	/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1385,8 @@ truncate_check_rel(Relation rel)
 	AclResult	aclresult;
 
 	/* Only allow truncate on regular tables */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table",
@@ -1521,6 +1614,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
+		/* Cannot inherit from partitioned tables */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partitioned table \"%s\"",
+							parent->relname)));
+
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
 			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
@@ -2166,7 +2266,8 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_COMPOSITE_TYPE &&
 		relkind != RELKIND_INDEX &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
@@ -4291,6 +4392,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			actual_target = ATT_TABLE;
 			break;
 		case RELKIND_VIEW:
@@ -4527,7 +4629,8 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
 		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
 
 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
-			rel->rd_rel->relkind == RELKIND_MATVIEW)
+			rel->rd_rel->relkind == RELKIND_MATVIEW ||
+			rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			if (origTypeName)
 				ereport(ERROR,
@@ -5250,6 +5353,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If the table is a range partitioned table, check that the column
+	 * is not in the partition key.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionKey	key = RelationGetPartitionKey(rel);
+		int				partnatts = get_partition_natts(key),
+						i;
+
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber	partattnum = get_partition_col_attnum(key, i);
+
+			if (partattnum == attnum)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("column \"%s\" is in range partition key",
+								colName)));
+		}
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5419,7 +5544,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_INDEX &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
@@ -5692,6 +5818,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 }
 
 /*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression.  It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false.  That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+	PartitionKey	key;
+	int				partnatts;
+	List		   *partexprs;
+	ListCell	   *partexprs_item;
+	int				i;
+
+	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		return false;
+
+	key = RelationGetPartitionKey(rel);
+	partnatts = get_partition_natts(key);
+	partexprs = get_partition_exprs(key);
+
+	partexprs_item = list_head(partexprs);
+	for (i = 0; i < partnatts; i++)
+	{
+		AttrNumber	partattno = get_partition_col_attnum(key, i);
+
+		if (partattno != 0)
+		{
+			if (attnum == partattno)
+			{
+				if (used_in_expr)
+					*used_in_expr = false;
+				return true;
+			}
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node	   *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+
+			/* Find all attributes referenced */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+							  expr_attrs))
+			{
+				if (used_in_expr)
+					*used_in_expr = true;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/*
  * Return value is the address of the dropped column.
  */
 static ObjectAddress
@@ -5705,6 +5893,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	AttrNumber	attnum;
 	List	   *children;
 	ObjectAddress object;
+	bool		is_expr;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5749,6 +5938,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 errmsg("cannot drop inherited column \"%s\"",
 						colName)));
 
+	/* Don't drop columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot drop column referenced in partition key expression")));
+	}
+
 	ReleaseSysCache(tuple);
 
 	/*
@@ -6267,6 +6469,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Validity checks (permission checks wait till we have the column
 	 * numbers)
 	 */
+	if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot reference partitioned table \"%s\"",
+						RelationGetRelationName(pkrel))));
+
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7904,6 +8112,7 @@ ATPrepAlterColumnType(List **wqueue,
 	NewColumnValue *newval;
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
+	bool		is_expr;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -7934,6 +8143,19 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter columns used in the partition key */
+	if (is_partition_attr(rel, attnum, &is_expr))
+	{
+		if (!is_expr)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column named in partition key")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter type of column referenced in partition key expression")));
+	}
+
 	/* Look up the target type */
 	typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
 
@@ -7949,7 +8171,8 @@ ATPrepAlterColumnType(List **wqueue,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   false);
 
-	if (tab->relkind == RELKIND_RELATION)
+	if (tab->relkind == RELKIND_RELATION ||
+		tab->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Set up an expression to transform the old data value to the new
@@ -8979,6 +9202,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_PARTITIONED_TABLE:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -9440,6 +9664,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_VIEW:
@@ -9860,7 +10085,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
 		/* Only move the object type requested */
 		if ((stmt->objtype == OBJECT_TABLE &&
-			 relForm->relkind != RELKIND_RELATION) ||
+			 relForm->relkind != RELKIND_RELATION &&
+			 relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
 			(stmt->objtype == OBJECT_INDEX &&
 			 relForm->relkind != RELKIND_INDEX) ||
 			(stmt->objtype == OBJECT_MATVIEW &&
@@ -10059,6 +10285,11 @@ ATPrepAddInherit(Relation child_rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
+
+	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of partitioned table")));
 }
 
 /*
@@ -10110,6 +10341,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 		 errmsg("cannot inherit to temporary relation of another session")));
 
+	/* Prevent partitioned tables from becoming inheritance parents */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(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.
@@ -11499,7 +11737,8 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
 
 	/* Fix other dependent stuff */
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
-		rel->rd_rel->relkind == RELKIND_MATVIEW)
+		rel->rd_rel->relkind == RELKIND_MATVIEW ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
 		AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
@@ -11948,7 +12187,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
 	if (!relkind)
 		return;
 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
-		relkind != RELKIND_MATVIEW)
+		relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12105,7 +12344,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 		relkind != RELKIND_VIEW &&
 		relkind != RELKIND_MATVIEW &&
 		relkind != RELKIND_SEQUENCE &&
-		relkind != RELKIND_FOREIGN_TABLE)
+		relkind != RELKIND_FOREIGN_TABLE &&
+		relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
@@ -12113,3 +12353,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	ReleaseSysCache(tuple);
 }
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+	PartitionSpec  *newspec;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	ListCell	   *l;
+
+	newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+	newspec->strategy = partspec->strategy;
+	newspec->location = partspec->location;
+	newspec->partParams = NIL;
+
+	/* Parse partitioning strategy name */
+	if (!pg_strcasecmp(partspec->strategy, "list"))
+		*strategy = PARTITION_STRATEGY_LIST;
+	else if (!pg_strcasecmp(partspec->strategy, "range"))
+		*strategy = PARTITION_STRATEGY_RANGE;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized partitioning strategy \"%s\"",
+						partspec->strategy)));
+
+	/*
+	 * Create a dummy ParseState and insert the target relation as its sole
+	 * rangetable entry.  We need a ParseState for transformExpr.
+	 */
+	pstate = make_parsestate(NULL);
+	rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
+	/* take care of any partition expressions */
+	foreach(l, partspec->partParams)
+	{
+		ListCell	   *lc;
+		PartitionElem  *pelem = (PartitionElem *) lfirst(l);
+
+		/* Check for PARTITION BY ... (foo, foo) */
+		foreach(lc, newspec->partParams)
+		{
+			PartitionElem	*pparam = (PartitionElem *) lfirst(lc);
+
+			if (pelem->name && pparam->name &&
+					!strcmp(pelem->name, pparam->name))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_COLUMN),
+						 errmsg("column \"%s\" appears more than once in partition key",
+								pelem->name),
+						 parser_errposition(pstate, pelem->location)));
+		}
+
+		if (pelem->expr)
+		{
+			/* Now do parse transformation of the expression */
+			pelem->expr = transformExpr(pstate, pelem->expr,
+										EXPR_KIND_PARTITION_EXPRESSION);
+
+			/* we have to fix its collations too */
+			assign_expr_collations(pstate, pelem->expr);
+		}
+
+		newspec->partParams = lappend(newspec->partParams, pelem);
+	}
+
+	return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+					  List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+	int			attn;
+	ListCell   *lc;
+
+	attn = 0;
+	foreach(lc, partParams)
+	{
+		PartitionElem  *pelem = (PartitionElem *) lfirst(lc);
+		Oid		atttype;
+		Oid		attcollation;
+
+		if (pelem->name != NULL)
+		{
+			/* Simple attribute reference */
+			HeapTuple   atttuple;
+			Form_pg_attribute attform;
+
+			atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+			if (!HeapTupleIsValid(atttuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" named in partition key does not exist",
+								pelem->name)));
+			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+			if (attform->attnum <= 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("cannot use system column \"%s\" in partition key",
+								pelem->name)));
+
+			partattrs[attn] = attform->attnum;
+			atttype = attform->atttypid;
+			attcollation = attform->attcollation;
+			ReleaseSysCache(atttuple);
+
+			/* Note that whole-row references can't happen here; see below */
+		}
+		else
+		{
+			/* Expression */
+			Node	   *expr = pelem->expr;
+
+			Assert(expr != NULL);
+			atttype = exprType(expr);
+			attcollation = exprCollation(expr);
+
+			/*
+			 * Strip any top-level COLLATE clause.  This ensures that we treat
+			 * "x COLLATE y" and "(x COLLATE y)" alike.
+			 */
+			while (IsA(expr, CollateExpr))
+				expr = (Node *) ((CollateExpr *) expr)->arg;
+
+			if (IsA(expr, Var) &&
+				((Var *) expr)->varattno != InvalidAttrNumber)
+			{
+				/*
+				 * User wrote "(column)" or "(column COLLATE something)".
+				 * Treat it like simple attribute anyway.
+				 */
+				partattrs[attn] = ((Var *) expr)->varattno;
+			}
+			else
+			{
+				Bitmapset	*expr_attrs = NULL;
+
+				partattrs[attn] = 0; 	/* marks the column as expression */
+				*partexprs = lappend(*partexprs, expr);
+
+				/*
+				 * Note that expression_planner does not change the passed in
+				 * expression destructively and we have already saved the
+				 * expression to be stored into the catalog above.
+				 */
+				expr = (Node *) expression_planner((Expr *) expr);
+
+				/*
+				 * Partition expression cannot contain mutable functions,
+				 * because a given row must always map to the same partition
+				 * as long as there is no change in the partition boundary
+				 * structure.
+				 */
+				if (contain_mutable_functions(expr))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+				/*
+				 * While it is not exactly *wrong* for an expression to be
+				 * a constant value, it seems better to prevent such input.
+				 */
+				if (IsA(expr, Const))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("cannot use constant expression as partition key")));
+
+				/*
+				 * transformPartitionSpec() should have already rejected subqueries,
+				 * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+				 * for partition expressions.
+				 */
+
+				/* Cannot have expressions containing whole-row references */
+				pull_varattnos(expr, 1, &expr_attrs);
+				if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								  expr_attrs))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("partition key expressions cannot contain whole-row references")));
+			}
+		}
+
+		/*
+		 * Apply collation override if any
+		 */
+		if (pelem->collation)
+			attcollation = get_collation_oid(pelem->collation, false);
+
+		/*
+		 * Check we have a collation iff it's a collatable type.  The only
+		 * expected failures here are (1) COLLATE applied to a noncollatable
+		 * type, or (2) partition expression had an unresolved collation.
+		 * But we might as well code this to be a complete consistency check.
+		 */
+		if (type_is_collatable(atttype))
+		{
+			if (!OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_INDETERMINATE_COLLATION),
+						 errmsg("could not determine which collation to use for partition expression"),
+						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+		else
+		{
+			if (OidIsValid(attcollation))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("collations are not supported by type %s",
+								format_type_be(atttype))));
+		}
+
+		partcollation[attn] = attcollation;
+
+		/*
+		 * Identify a btree opclass to use. Currently, we use only btree
+		 * operators, which seems enough for list and range partitioning.
+		 */
+		if (!pelem->opclass)
+		{
+			partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+			if (!OidIsValid(partopclass[attn]))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("data type %s has no default btree operator class",
+								format_type_be(atttype)),
+						 errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+		}
+		else
+			partopclass[attn] = ResolveOpClass(pelem->opclass,
+											   atttype,
+											   "btree",
+											   BTREE_AM_OID);
+
+		attn++;
+	}
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7736..02e9693f28 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	 * Triggers must be on tables or views, and there are additional
 	 * relation-type-specific restrictions.
 	 */
-	if (rel->rd_rel->relkind == RELKIND_RELATION)
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Tables can't have INSTEAD OF triggers */
 		if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 					 errmsg("\"%s\" is a table",
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
+		/* Disallow ROW triggers on partitioned tables */
+		if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a partitioned table",
+							RelationGetRelationName(rel)),
+					 errdetail("Partitioned tables cannot have ROW triggers.")));
 	}
 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
 	{
@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
-		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
-		form->relkind != RELKIND_FOREIGN_TABLE)
+		form->relkind != RELKIND_FOREIGN_TABLE &&
+		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf5548b..b1be2f7ad5 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
 	 */
 	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
-		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
+		onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
+		onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 	{
 		ereport(WARNING,
 				(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 71c07288a1..badca109a3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 	switch (resultRel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 	switch (rel->rd_rel->relkind)
 	{
 		case RELKIND_RELATION:
+		case RELKIND_PARTITIONED_TABLE:
 			/* OK */
 			break;
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e8e5..29d5f5786a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,7 +1886,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 					if (relkind == RELKIND_RELATION ||
-						relkind == RELKIND_MATVIEW)
+						relkind == RELKIND_MATVIEW ||
+						relkind == RELKIND_PARTITIONED_TABLE)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index dd66adb0b2..5d1a1d46fa 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partspec);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4188,6 +4189,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+	PartitionSpec *newnode = makeNode(PartitionSpec);
+
+	COPY_STRING_FIELD(strategy);
+	COPY_NODE_FIELD(partParams);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+	PartitionElem *newnode = makeNode(PartitionElem);
+
+	COPY_STRING_FIELD(name);
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(collation);
+	COPY_NODE_FIELD(opclass);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5105,6 +5133,12 @@ copyObject(const void *from)
 		case T_TriggerTransition:
 			retval = _copyTriggerTransition(from);
 			break;
+		case T_PartitionSpec:
+			retval = _copyPartitionSpec(from);
+			break;
+		case T_PartitionElem:
+			retval = _copyPartitionElem(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index cad3aebecd..3c3159851f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partspec);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2646,6 +2647,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
 	return true;
 }
 
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+	COMPARE_STRING_FIELD(strategy);
+	COMPARE_NODE_FIELD(partParams);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+	COMPARE_STRING_FIELD(name);
+	COMPARE_NODE_FIELD(expr);
+	COMPARE_NODE_FIELD(collation);
+	COMPARE_NODE_FIELD(opclass);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3402,6 +3425,12 @@ equal(const void *a, const void *b)
 		case T_TriggerTransition:
 			retval = _equalTriggerTransition(a, b);
 			break;
+		case T_PartitionSpec:
+			retval = _equalPartitionSpec(a, b);
+			break;
+		case T_PartitionElem:
+			retval = _equalPartitionElem(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 748b687929..323daf5081 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partspec);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
 		appendStringInfo(str, " %u", node->conpfeqop[i]);
 }
 
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBY");
+
+	WRITE_STRING_FIELD(strategy);
+	WRITE_NODE_FIELD(partParams);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+	WRITE_NODE_TYPE("PARTITIONELEM");
+
+	WRITE_STRING_FIELD(name);
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(collation);
+	WRITE_NODE_FIELD(opclass);
+	WRITE_LOCATION_FIELD(location);
+}
 
 /*
  * outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
 			case T_TriggerTransition:
 				_outTriggerTransition(str, obj);
 				break;
+			case T_PartitionSpec:
+				_outPartitionSpec(str, obj);
+				break;
+			case T_PartitionElem:
+				_outPartitionElem(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 414348b95b..bbf5fba357 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct ImportQual	*importqual;
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
+	PartitionElem		*partelem;
+	PartitionSpec		*partspec;
 }
 
 %type <node>	stmt schema_stmt
@@ -545,6 +547,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <partspec>	PartitionSpec OptPartitionSpec
+%type <str>			part_strategy
+%type <partelem>	part_elem
+%type <list>		part_params
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptWith OnCommitOption
-			OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
 					n->inhRelations = NIL;
+					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $8;
-					n->oncommit = $9;
-					n->tablespacename = $10;
+					n->options = $9;
+					n->oncommit = $10;
+					n->tablespacename = $11;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+			OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
 					n->inhRelations = NIL;
+					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec	{ $$ = $1; }
+			| /*EMPTY*/			{ $$ = NULL; }
+		;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+				{
+					PartitionSpec *n = makeNode(PartitionSpec);
+
+					n->strategy = $3;
+					n->partParams = $5;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
+
+part_strategy:	IDENT					{ $$ = $1; }
+				| unreserved_keyword	{ $$ = pstrdup($1); }
+		;
+
+part_params:	part_elem						{ $$ = list_make1($1); }
+			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+		;
+
+part_elem: ColId opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = $1;
+					n->expr = NULL;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| func_expr_windowless opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $1;
+					n->collation = $2;
+					n->opclass = $3;
+					n->location = @1;
+					$$ = n;
+				}
+			| '(' a_expr ')' opt_collate opt_class
+				{
+					PartitionElem *n = makeNode(PartitionElem);
+
+					n->name = NULL;
+					n->expr = $2;
+					n->collation = $4;
+					n->opclass = $5;
+					n->location = @1;
+					$$ = n;
+				}
+		;
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4ddc48..92d1577030 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in trigger WHEN conditions");
 
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in partition key expression");
+			else
+				err = _("grouping operations are not allowed in partition key expression");
+
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("window functions are not allowed in partition key expression");
+			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 17d1cbf8b3..8a2bdf06e8 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("cannot use subquery in trigger WHEN condition");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("cannot use subquery in partition key expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			return "PARTITION BY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a4293d..7d9b4157d4 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("set-returning functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_PARTITION_EXPRESSION:
+			err = _("set-returning functions are not allowed in partition key expression");
+			break;
 
 			/*
 			 * 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 0670bc2482..fc896a27fe 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
 	List	   *alist;			/* "after list" of things to do after creating
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
+	bool		ispartitioned;	/* true if table is partitioned */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = stmt->partspec != NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partspec)
+	{
+		int		partnatts = list_length(stmt->partspec->partParams);
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot create partitioned table as inheritance child")));
+
+		if (partnatts > PARTITION_MAX_KEYS)
+			ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("cannot partition using more than %d columns",
+						PARTITION_MAX_KEYS)));
+
+		if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+			partnatts > 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot list partition using more than one column")));
+	}
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.  We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("primary key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("primary key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				/* FALL THRU */
 
 			case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("unique constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 				if (constraint->keys == NIL)
 					constraint->keys = list_make1(makeString(column->colname));
 				cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 							 errmsg("foreign key constraints are not supported on foreign tables"),
 							 parser_errposition(cxt->pstate,
 												constraint->location)));
+				if (cxt->ispartitioned)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("foreign key constraints are not supported on partitioned tables"),
+							 parser_errposition(cxt->pstate,
+												constraint->location)));
 
 				/*
 				 * Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("primary key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("primary key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("unique constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("unique constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("exclusion constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("exclusion constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
 			break;
 
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
 						 errmsg("foreign key constraints are not supported on foreign tables"),
 						 parser_errposition(cxt->pstate,
 											constraint->location)));
+			if (cxt->ispartitioned)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("foreign key constraints are not supported on partitioned tables"),
+						 parser_errposition(cxt->pstate,
+											constraint->location)));
 			cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
 			break;
 
@@ -763,7 +829,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		relation->rd_rel->relkind != RELKIND_VIEW &&
 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
 		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
-		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+		relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
@@ -1854,7 +1921,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				rel = heap_openrv(inh, AccessShareLock);
 				/* check user requested inheritance from valid relkind */
 				if (rel->rd_rel->relkind != RELKIND_RELATION &&
-					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+					rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+					rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
+	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891c34..32e1328149 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename,
 	 */
 	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
-		event_relation->rd_rel->relkind != RELKIND_VIEW)
+		event_relation->rd_rel->relkind != RELKIND_VIEW &&
+		event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a table or view",
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e081..bf4f098c15 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
-		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
+		target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
+		target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/*
 		 * Emit CTID so that executor can find the row to update or delete.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index b7edefc7dd..e38586dd80 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
 	*hasSubLinks = false;
 
 	/* If this is not a normal relation, just return immediately */
-	if (rte->relkind != RELKIND_RELATION)
+	if (rte->relkind != RELKIND_RELATION &&
+		rte->relkind != RELKIND_PARTITIONED_TABLE)
 		return;
 
 	/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1ff48..7f3ba74db2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
+#include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
@@ -49,6 +50,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -435,6 +439,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 		case RELKIND_INDEX:
 		case RELKIND_VIEW:
 		case RELKIND_MATVIEW:
+		case RELKIND_PARTITIONED_TABLE:
 			break;
 		default:
 			return;
@@ -796,6 +801,236 @@ RelationBuildRuleLock(Relation relation)
 }
 
 /*
+ * RelationBuildPartitionKey
+ *		Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache.  To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information.  That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple		tuple;
+	bool			isnull;
+	int				i;
+	PartitionKey	key;
+	AttrNumber	   *attrs;
+	oidvector	   *opclass;
+	oidvector	   *collation;
+	ListCell	   *partexprs_item;
+	Datum			datum;
+	MemoryContext	partkeycxt,
+					oldcxt;
+
+	tuple = SearchSysCache1(PARTRELID,
+							ObjectIdGetDatum(RelationGetRelid(relation)));
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned_table entry yet.
+	 */
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+	/* Fixed-length attributes */
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	key->strategy = form->partstrat;
+	key->partnatts = form->partnatts;
+
+	/*
+	 * We can rely on the first variable-length attribute being mapped to
+	 * the relevant field of the catalog's C struct, because all previous
+	 * attributes are non-nullable and fixed-length.
+	 */
+	attrs = form->partattrs.values;
+
+	/* But use the hard way to retrieve further variable-length attributes */
+	/* Operator class */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(datum);
+
+	/* Collation */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	collation = (oidvector *) DatumGetPointer(datum);
+
+	/* Expressions */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partexprs, &isnull);
+	if (!isnull)
+	{
+		char   *exprString;
+		Node   *expr;
+
+		exprString = TextDatumGetCString(datum);
+		expr = stringToNode(exprString);
+		pfree(exprString);
+
+		/*
+		 * Run the expressions through const-simplification since the planner
+		 * will be comparing them to similarly-processed qual clause operands,
+		 * and may fail to detect valid matches without this step.  We don't
+		 * need to bother with canonicalize_qual() though, because partition
+		 * expressions are not full-fledged qualification clauses.
+		 */
+		expr = eval_const_expressions(NULL, (Node *) expr);
+
+		/* May as well fix opfuncids too */
+		fix_opfuncids((Node *) expr);
+		key->partexprs = (List *) expr;
+	}
+
+	key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+	key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+	key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Gather type and collation info as well */
+	key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+	key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+	key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+	key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+	key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+	key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+	/* Copy partattrs and fill other per-attribute info */
+	memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber		attno = key->partattrs[i];
+		HeapTuple		opclasstup;
+		Form_pg_opclass opclassform;
+		Oid				funcid;
+
+		/* Collect opfamily information */
+		opclasstup = SearchSysCache1(CLAOID,
+									 ObjectIdGetDatum(opclass->values[i]));
+		if (!HeapTupleIsValid(opclasstup))
+			elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+		opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
+		key->partopfamily[i] = opclassform->opcfamily;
+		key->partopcintype[i] = opclassform->opcintype;
+
+		/*
+		 * A btree support function covers the cases of list and range methods
+		 * currently supported.
+		 */
+		funcid = get_opfamily_proc(opclassform->opcfamily,
+								   opclassform->opcintype,
+								   opclassform->opcintype,
+								   BTORDER_PROC);
+
+		fmgr_info(funcid, &key->partsupfunc[i]);
+
+		/* Collation */
+		key->partcollation[i] = collation->values[i];
+
+		/* Collect type information */
+		if (attno != 0)
+		{
+			key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+			key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+			key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+		}
+		else
+		{
+			key->parttypid[i] = exprType(lfirst(partexprs_item));
+			key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+			key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+		}
+		get_typlenbyvalalign(key->parttypid[i],
+							 &key->parttyplen[i],
+							 &key->parttypbyval[i],
+							 &key->parttypalign[i]);
+
+		ReleaseSysCache(opclasstup);
+	}
+
+	ReleaseSysCache(tuple);
+
+	/* Success --- now copy to the cache memory */
+	partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_SIZES);
+	relation->rd_partkeycxt = partkeycxt;
+	oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+	relation->rd_partkey = copy_partition_key(key);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+	PartitionKey	newkey;
+	int				n;
+
+	newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+	newkey->strategy = fromkey->strategy;
+	newkey->partnatts = n = fromkey->partnatts;
+
+	newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+	memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+	newkey->partexprs = copyObject(fromkey->partexprs);
+
+	newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+	newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+	newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+	memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+	newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+	newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+	newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+	memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+	newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+	memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+	newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+	newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+	memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+	newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+	memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+	return newkey;
+}
+
+/*
  *		equalRuleLocks
  *
  *		Determine whether two RuleLocks are equivalent
@@ -1050,6 +1285,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
+	/* if it's a partitioned table, initialize key info */
+	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		RelationBuildPartitionKey(relation);
+	else
+	{
+		relation->rd_partkeycxt = NULL;
+		relation->rd_partkey = NULL;
+	}
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -2042,6 +2286,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rulescxt);
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
+	if (relation->rd_partkeycxt)
+		MemoryContextDelete(relation->rd_partkeycxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2983,7 +3229,9 @@ RelationBuildLocalRelation(const char *relname,
 
 	/* system relations and non-table objects don't have one */
 	if (!IsSystemNamespace(relnamespace) &&
-		(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+		(relkind == RELKIND_RELATION ||
+		 relkind == RELKIND_MATVIEW ||
+		 relkind == RELKIND_PARTITIONED_TABLE))
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
 	else
 		rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3762,17 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4267,6 +4526,8 @@ RelationGetIndexExpressions(Relation relation)
 	 */
 	result = (List *) eval_const_expressions(NULL, (Node *) result);
 
+	result = (List *) canonicalize_qual((Expr *) result);
+
 	/* May as well fix opfuncids too */
 	fix_opfuncids((Node *) result);
 
@@ -5035,6 +5296,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = NULL;
+		rel->rd_partkeycxt = NULL;
+		rel->rd_partkey = NULL;
 		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 65ffe84409..a3e0517b94 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionedRelationId,		/* PARTRELID */
+		PartitionedRelidIndexId,
+		1,
+		{
+			Anum_pg_partitioned_table_partrelid,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{ProcedureRelationId,		/* PROCNAMEARGSNSP */
 		ProcedureNameArgsNspIndexId,
 		3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 4d84a6ba08..e8a302f2fd 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -189,7 +189,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
 extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
-								DependencyType self_behavior);
+								DependencyType self_behavior,
+								bool ignore_self);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8b21..11b16a93e8 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
 				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+					char strategy,
+					int16 partnatts,
+					AttrNumber *partattrs,
+					List *partexprs,
+					Oid *partopclass,
+					Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d417..40f7576b7b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId          3351
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c417..6a86c93efb 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -161,6 +161,7 @@ DESCR("");
 #define		  RELKIND_COMPOSITE_TYPE  'c'		/* composite type */
 #define		  RELKIND_FOREIGN_TABLE   'f'		/* foreign table */
 #define		  RELKIND_MATVIEW		  'm'		/* materialized view */
+#define		  RELKIND_PARTITIONED_TABLE 'P'		/* partitioned table */
 
 #define		  RELPERSISTENCE_PERMANENT	'p'		/* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u'		/* unlogged permanent table */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000000..cec54ae62e
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ *	  definition of the system "partitioned table" relation
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_table definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+	Oid				partrelid;		/* partitioned table oid */
+	char			partstrat;		/* partitioning strategy */
+	int16			partnatts;		/* number of partition key columns */
+
+	/*
+	 * variable-length fields start here, but we allow direct access to
+	 * partattrs via the C struct.  That's because the first variable-length
+	 * field of a heap tuple can be reliably accessed using its C struct
+	 * offset, as previous fields are all non-nullable fixed-length fields.
+	 */
+	int2vector		partattrs;		/* each member of the array is the
+									 * attribute number of a partition key
+									 * column, or 0 if the column is actually
+									 * an expression */
+
+#ifdef CATALOG_VARLEN
+	oidvector		partclass;		/* operator class to compare keys */
+	oidvector		partcollation;	/* user-specified collation for keys */
+	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+									 * key; one item for each zero entry in
+									 * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ *      Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ *      the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ *      compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table				7
+#define Anum_pg_partitioned_table_partrelid		1
+#define Anum_pg_partitioned_table_partstrat		2
+#define Anum_pg_partitioned_table_partnatts		3
+#define Anum_pg_partitioned_table_partattrs		4
+#define Anum_pg_partitioned_table_partclass		5
+#define Anum_pg_partitioned_table_partcollation	6
+#define Anum_pg_partitioned_table_partexprs		7
+
+#endif   /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff262..d790fbfee2 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
 					 List *attributeList,
 					 List *exclusionOpNames);
 extern Oid	GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid	ResolveOpClass(List *opclass, Oid attrType,
+			   char *accessMethodName, Oid accessMethodId);
 
 /* commands/functioncmds.c */
 extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307cd00..b27412cae3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
+	T_PartitionElem,
+	T_PartitionSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f8003e46f3..01c2f93a6f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
 	int			location;		/* token location, or -1 if unknown */
 } XmlSerialize;
 
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+	NodeTag		type;
+	char	   *name;		/* name of column to partition on, or NULL */
+	Node	   *expr;		/* expression to partition on, or NULL */
+	List	   *collation;	/* name of collation; NIL = default */
+	List	   *opclass;	/* name of desired opclass; NIL = default */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+	NodeTag		type;
+	char	   *strategy;	/* partitioning strategy ('list' or 'range') */
+	List	   *partParams; /* List of PartitionElems */
+	int			location;	/* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST		'l'
+#define PARTITION_STRATEGY_RANGE	'r'
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	PartitionSpec *partspec;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 66335863db..bd6dc020b2 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_ALTER_COL_TRANSFORM,		/* transform expr in ALTER COLUMN TYPE */
 	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_POLICY,			/* USING or WITH CHECK expr in policy */
+	EXPR_KIND_PARTITION_EXPRESSION	/* PARTITION BY expression */
 } ParseExprKind;
 
 
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 96885bb990..58b1db9f68 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
 #define INDEX_MAX_KEYS		32
 
 /*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS	32
+
+/*
  * Set the upper and lower bounds of sequence values.
  */
 #define SEQ_MAXVALUE	PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28468..60d8de37f1 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+	char		strategy;		/* partitioning strategy */
+	int16		partnatts;		/* number of columns in the partition key */
+	AttrNumber *partattrs;		/* attribute numbers of columns in the
+								 * partition key */
+	List	   *partexprs;		/* list of expressions in the partitioning
+								 * key, or NIL */
+
+	Oid		   *partopfamily;	/* OIDs of operator families */
+	Oid		   *partopcintype;	/* OIDs of opclass declared input data types */
+	FmgrInfo   *partsupfunc;	/* lookup info for support funcs */
+
+	/* Partitioning collation per attribute */
+	Oid		   *partcollation;
+
+	/* Type information per attribute */
+	Oid		   *parttypid;
+	int32	   *parttypmod;
+	int16	   *parttyplen;
+	bool	   *parttypbyval;
+	char	   *parttypalign;
+	Oid		   *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
 
 /*
  * Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
 	List	   *rd_fkeylist;	/* list of ForeignKeyCacheInfo (see below) */
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
+	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
+	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
 	 RelationNeedsWAL(relation) && \
 	 !IsCatalogRelation(relation))
 
+/*
+ * RelationGetPartitionKey
+ *		Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+	return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+	return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+	return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+	return key->partattrs[col];
+}
+
 /* 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 256615b671..39fe947d6e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
 	RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3dfa..df6fe13c3a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,49 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
  c4     | integer |           |          | 
 
 DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+                                    ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+                                    ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+                                    ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+                                    ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR:  cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR:  cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR:  cannot alter type of column referenced in partition key expression
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+ERROR:  column "a" is in range partition key
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR:  cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR:  cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb874e8..410d96b0b6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,171 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR:  cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+ERROR:  cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR:  primary key constraints are not supported on partitioned tables
+LINE 2:  a int PRIMARY KEY
+               ^
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR:  foreign key constraints are not supported on partitioned tables
+LINE 2:  a int REFERENCES pkrel(a)
+               ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR:  unique constraints are not supported on partitioned tables
+LINE 2:  a int UNIQUE
+               ^
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 3:  EXCLUDE USING gist (a WITH &&)
+         ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+ERROR:  column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+ERROR:  set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR:  aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR:  window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR:  cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+ERROR:  cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+ERROR:  cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+ERROR:  unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+ERROR:  column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+ERROR:  cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR:  functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+ERROR:  partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+ERROR:  data type point has no default btree operator class
+HINT:  You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+ERROR:  operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind 
+---------
+ P
+(1 row)
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+ attname | attnotnull 
+---------+------------
+ a       | t
+ b       | f
+ c       | t
+ d       | t
+(4 rows)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR:  cannot drop function plusone(integer) because other objects depend on it
+DETAIL:  table partitioned depends on function plusone(integer)
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR:  cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf60d2..8fa929a6aa 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partitioned_table|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3ec64..ec61b02c57 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,35 @@ ALTER TABLE test_add_column
 	ADD COLUMN c4 integer;
 \d test_add_column
 DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- cannot drop NOT NULL on columns in the range partition key
+ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+	a int,
+	b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8bf5e..b9489fc171 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,149 @@ DROP TABLE as_select1;
 -- check that the oid column is added before the primary key is checked
 CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
 DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+	a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+	a1 int,
+	a2 int
+) PARTITION BY LIST (a1, a2);	-- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+	a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+	a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+	a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+	a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+	a int,
+	EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+	a int,
+	b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+	a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+	a	int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+	a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+	a int,
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+	a int,
+	b int,
+	c text,
+	d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- check that range partition key columns are marked NOT NULL
+SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
-- 
2.11.0

0002-psql-and-pg_dump-support-for-partitioned-tables-20.patchtext/x-diff; name=0002-psql-and-pg_dump-support-for-partitioned-tables-20.patchDownload
From fb336f99089d6a22b9e56aa76a3992545b1543c5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/7] psql and pg_dump support for partitioned tables.

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          | 159 +++++++++++++++++++++++++++++
 src/bin/pg_dump/common.c                   |   4 +
 src/bin/pg_dump/pg_dump.c                  |  68 ++++++++++--
 src/bin/pg_dump/pg_dump.h                  |   2 +
 src/bin/psql/describe.c                    |  61 ++++++++---
 src/bin/psql/tab-complete.c                |   6 +-
 src/include/catalog/pg_proc.h              |   2 +
 src/include/utils/builtins.h               |   1 +
 src/test/regress/expected/create_table.out |  20 +++-
 src/test/regress/sql/create_table.sql      |   6 +-
 10 files changed, 302 insertions(+), 27 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fecee85e5b..60fe794816 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1415,6 +1417,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	oidvector  *partcollation;
+	List	   *partexprs;
+	ListCell   *partexpr_item;
+	List	   *context;
+	Datum		datum;
+	bool		isnull;
+	StringInfoData buf;
+	int			keyno;
+	char	   *str;
+	char	   *sep;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+	form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass and partcollation the hard way */
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partclass, &isnull);
+	Assert(!isnull);
+	partclass = (oidvector *) DatumGetPointer(datum);
+
+	datum = SysCacheGetAttr(PARTRELID, tuple,
+							Anum_pg_partitioned_table_partcollation, &isnull);
+	Assert(!isnull);
+	partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+	/*
+	 * Get the expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions, because we want to display non-const-folded
+	 * expressions.)
+	 */
+	if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+									 Anum_pg_partitioned_table_partexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		partexprs = (List *) stringToNode(exprsString);
+
+		if (!IsA(partexprs, List))
+			elog(ERROR, "unexpected node type found in partexprs: %d",
+						(int) nodeTag(partexprs));
+
+		pfree(exprsString);
+	}
+	else
+		partexprs = NIL;
+
+	partexpr_item = list_head(partexprs);
+	context = deparse_context_for(get_relation_name(relid), relid);
+
+	initStringInfo(&buf);
+
+	switch (form->partstrat)
+	{
+		case PARTITION_STRATEGY_LIST:
+			appendStringInfo(&buf, "LIST");
+			break;
+		case PARTITION_STRATEGY_RANGE:
+			appendStringInfo(&buf, "RANGE");
+			break;
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) form->partstrat);
+	}
+
+	appendStringInfo(&buf, " (");
+	sep = "";
+	for (keyno = 0; keyno < form->partnatts; keyno++)
+	{
+		AttrNumber	attnum = form->partattrs.values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			partcoll;
+
+		appendStringInfoString(&buf, sep);
+		sep = ", ";
+		if (attnum != 0)
+		{
+			/* Simple attribute reference */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(relid, attnum);
+			appendStringInfoString(&buf, quote_identifier(attname));
+			get_atttypetypmodcoll(relid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* Expression */
+			Node	   *partkey;
+
+			if (partexpr_item == NULL)
+				elog(ERROR, "too few entries in partexprs list");
+			partkey = (Node *) lfirst(partexpr_item);
+			partexpr_item = lnext(partexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(partkey, context, false, false,
+											0, 0);
+
+			appendStringInfoString(&buf, str);
+			keycoltype = exprType(partkey);
+			keycolcollation = exprCollation(partkey);
+		}
+
+		/* Add collation, if not default for column */
+		partcoll = partcollation->values[keyno];
+		if (OidIsValid(partcoll) && partcoll != keycolcollation)
+			appendStringInfo(&buf, " COLLATE %s",
+							 generate_collation_name((partcoll)));
+
+		/* Add the operator class name, if not default */
+		get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+	}
+	appendStringInfoChar(&buf, ')');
+
+	/* Clean up */
+	ReleaseSysCache(tuple);
+
+	return buf.data;
+}
 
 /*
  * pg_get_constraintdef
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb9874f3..3e20f028c8 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading partition key information for interesting tables\n");
+	getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 42873bb32a..2fb6d5dcc4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1239,9 +1239,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2098,6 +2099,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4993,7 +4997,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5007,7 +5011,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5535,7 +5540,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6934,6 +6941,47 @@ getTransforms(Archive *fout, int *numTransforms)
 }
 
 /*
+ * getTablePartitionKeyInfo -
+ *	  for each interesting partitioned table, read information about its
+ *	  partition key
+ *
+ *	modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+	PQExpBuffer q = createPQExpBuffer();
+	int			i,
+				ntups;
+	PGresult   *res;
+
+	/* No partitioned tables before 10 */
+	if (fout->remoteVersion < 100000)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &(tblinfo[i]);
+
+		/* Only partitioned tables have partition key */
+		if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+							 tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+		ntups = PQntuples(res);
+		Assert(ntups == 1);
+		tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+	}
+}
+
+/*
  * getTableAttrs -
  *	  for each interesting table, read info about its attributes
  *	  (names, types, default values, CHECK constraints, etc)
@@ -14343,6 +14391,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -14403,7 +14454,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
-			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
+			 tbinfo->relkind == RELKIND_FOREIGN_TABLE ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
 			{
@@ -14421,7 +14473,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -14638,6 +14691,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7df9066cd7..f47e535c24 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -312,6 +312,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
@@ -649,5 +650,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
 extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 931c6887f9..baa5e859d7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -865,6 +865,7 @@ permissionsList(const char *pattern)
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
 					  gettext_noop("Schema"),
@@ -874,6 +875,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"));
 
 	printACLColumn(&buf, "c.relacl");
@@ -954,7 +956,7 @@ permissionsList(const char *pattern)
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
-						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+						 "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
 
 	/*
 	 * Unless a schema pattern is specified, we suppress system and temp
@@ -1600,8 +1602,8 @@ describeOneTableDetails(const char *schemaname,
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
 		 */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+			tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 			appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
 	}
 
@@ -1666,6 +1668,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1679,8 +1689,8 @@ describeOneTableDetails(const char *schemaname,
 	cols = 2;
 
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-		tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+		tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+		tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
 	{
 		headers[cols++] = gettext_noop("Collation");
 		headers[cols++] = gettext_noop("Nullable");
@@ -1701,12 +1711,12 @@ describeOneTableDetails(const char *schemaname,
 	{
 		headers[cols++] = gettext_noop("Storage");
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'f')
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-			tableinfo.relkind == 'm' ||
-			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+			tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+			tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			headers[cols++] = gettext_noop("Description");
 	}
 
@@ -1782,7 +1792,7 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'f')
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
@@ -1790,14 +1800,33 @@ describeOneTableDetails(const char *schemaname,
 
 			/* Column comments, if the relkind supports this feature. */
 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
-				tableinfo.relkind == 'm' ||
-				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+				tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+				tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 		}
 	}
 
 	/* Make footers */
+	if (tableinfo.relkind == 'P')
+	{
+		/* Get the partition key information  */
+		PGresult   *result;
+		char	   *partkeydef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result || PQntuples(result) != 1)
+			goto error_return;
+
+		partkeydef = PQgetvalue(result, 0, 0);
+		printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
+		printTableAddFooter(&cont, tmpbuf.data);
+		PQclear(result);
+	}
+
 	if (tableinfo.relkind == 'i')
 	{
 		/* Footer information about an index */
@@ -1936,7 +1965,7 @@ describeOneTableDetails(const char *schemaname,
 		PQclear(result);
 	}
 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-			 tableinfo.relkind == 'f')
+			 tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		/* Footer information about a table */
 		PGresult   *result = NULL;
@@ -2513,7 +2542,7 @@ describeOneTableDetails(const char *schemaname,
 	 * Finish printing the footer information about a table.
 	 */
 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
-		tableinfo.relkind == 'f')
+		tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
 	{
 		PGresult   *result;
 		int			tuples;
@@ -2717,7 +2746,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
 					  Oid tablespace, const bool newline)
 {
 	/* relkinds for which we support tablespaces */
-	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+	if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
 	{
 		/*
 		 * We ignore the database default tablespace so that users not using
@@ -3051,6 +3080,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN 'S' THEN '%s'"
 					  " WHEN 's' THEN '%s'"
 					  " WHEN 'f' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3062,6 +3092,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("sequence"),
 					  gettext_noop("special"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("table"),	/* partitioned table */
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 
@@ -3100,7 +3131,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6b95052a67..cd64c39b7f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce71c..96e77ec437 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1979,6 +1979,8 @@ DATA(insert OID = 1642 (  pg_get_userbyid	   PGNSP PGUID 12 1 0 0 0 f f f f t f
 DESCR("role name by OID (with fallback)");
 DATA(insert OID = 1643 (  pg_get_indexdef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
 DESCR("index description");
+DATA(insert OID = 3352 (  pg_get_partkeydef	   PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
 DATA(insert OID = 1662 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
 DESCR("trigger description");
 DATA(insert OID = 1387 (  pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 90f5132b03..7ed162322c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 410d96b0b6..02e0720eec 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -417,7 +417,25 @@ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 ERROR:  cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+            Table "public.partitioned"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+ c      | text    |           | not null | 
+ d      | text    |           | not null | 
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+            Table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: LIST ((a + 1))
+
 DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index b9489fc171..2af3214b19 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -411,7 +411,11 @@ DROP FUNCTION plusone(int);
 -- partitioned table cannot partiticipate in regular inheritance
 CREATE TABLE partitioned2 (
 	a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
 CREATE TABLE fail () INHERITS (partitioned2);
 
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
 DROP TABLE partitioned, partitioned2;
-- 
2.11.0

0003-Catalog-and-DDL-for-partitions-20.patchtext/x-diff; name=0003-Catalog-and-DDL-for-partitions-20.patchDownload
From e69314cf5949d38c31fe34db3454c7fe7ce09277 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/7] Catalog and DDL for partitions.

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

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

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

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

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 126cfdfad8..9d2e89523d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf21e4..a6a43c4b30 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -33,6 +33,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
+ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
 
@@ -166,6 +170,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       values or to reject null values.  You can only use <literal>SET
       NOT NULL</> when the column contains no null values.
      </para>
+
+     <para>
+      If this table is a partition, one cannot perform <literal>DROP NOT NULL</>
+      on a column if it is marked <literal>NOT NULL</literal> in the parent
+      table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,13 +714,63 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+    <listitem>
+     <para>
+      This form attaches an existing table (which might itself be partitioned)
+      as a partition of the target table using the same syntax for
+      <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
+      <xref linkend="sql-createtable">.  The partition bound specification
+      must correspond to the partitioning strategy and partition key of the
+      target table.  The table to be attached must have all the same columns
+      as the target table and no more; moreover, the column types must also
+      match.  Also, it must have all the <literal>NOT NULL</literal> and
+      <literal>CHECK</literal> constraints of the target table.  Currently
+      <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered.
+      If any of the <literal>CHECK</literal> constraints of the table being
+      attached is marked <literal>NO INHERIT</literal>, the command will fail;
+      such a constraint must be recreated without the <literal>NO INHERIT</literal>
+      clause.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is
+      possible to avoid this scan by adding a valid <literal>CHECK</literal>
+      constraint to the table that would allow only the rows satisfying the
+      desired partition constraint before running this command.  It will be
+      determined using such a constraint that the table need not be scanned
+      to validate the partition constraint.  This does not work, however, if
+      any of the partition keys is an expression and the partition does not
+      accept <literal>NULL</literal> values.  If attaching a list partition
+      that will not accept <literal>NULL</literal> values, also add
+      <literal>NOT NULL</literal> constraint to the partition key column,
+      unless it's an expression.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
   <para>
    All the actions except <literal>RENAME</literal>,
-   <literal>SET TABLESPACE</literal> and <literal>SET SCHEMA</literal>
-   can be combined into
+   <literal>SET TABLESPACE</literal>, <literal>SET SCHEMA</literal>,
+   <literal>ATTACH PARTITION</literal>, and
+   <literal>DETACH PARTITION</literal> can be combined into
    a list of multiple alterations to apply in parallel.  For example, it
    is possible to add several columns and/or alter the type of several
    columns in a single command.  This is particularly useful with large
@@ -721,8 +781,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, you must own the parent
+   table as well.  Also, to attach a table as a new partition of the table,
+   you must own the table being attached.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +999,25 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.  Refer to
+        <xref linkend="sql-createtable"> for more details on the syntax of the same.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
@@ -978,6 +1058,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition it may be scanned to verify that
+    existing rows meet the partition constraint.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1132,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1321,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
     ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
 </programlisting></para>
 
+  <para>
+   Attach a partition to range partitioned table:
+<programlisting>
+ALTER TABLE measurement
+    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Detach a partition from partitioned table:
+<programlisting>
+ALTER TABLE cities
+    DETACH PARTITION measurement_y2015m12;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033cb5..5d0dcf567b 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+  PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
+    SERVER server_07;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 3e747df17e..8bf8af302b 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> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ 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,11 @@ 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>
+
+{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) |
+  FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -232,6 +248,51 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partition key of the parent table, and must not overlap with
+      any existing partition of that parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Note that dropping a partition with <literal>DROP TABLE</literal>
+      requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the
+      parent table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1429,7 +1490,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca8ee..2d5ac09bec 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 596b29ef56..7f5bad0b5d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1761,6 +1769,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1768,6 +1778,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1858,6 +1883,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2464,8 +2495,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2510,10 +2544,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3172,3 +3220,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000000..0a4f95fc3f
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1592 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Information about bounds of a partitioned relation
+ *
+ * A list partition datum that is known to be NULL is never put into the
+ * datums array. Instead, it is tracked using has_null and null_index fields.
+ *
+ * In the case of range partitioning, ndatums will typically be far less than
+ * 2 * nparts, because a partition's upper bound and the next partition's lower
+ * bound are the same in most common cases, and we only store one of them.
+ *
+ * In the case of list partitioning, the indexes array stores one entry for
+ * every datum, which is the index of the partition that accepts a given datum.
+ * In case of range partitioning, it stores one entry per distinct range
+ * datum, which is the index of the partition for which a given datum
+ * is an upper bound.
+ */
+
+/* Ternary value to represent what's contained in a range bound datum */
+typedef enum RangeDatumContent
+{
+	RANGE_DATUM_FINITE = 0,		/* actual datum stored elsewhere */
+	RANGE_DATUM_NEG_INF,		/* negative infinity */
+	RANGE_DATUM_POS_INF			/* positive infinity */
+} RangeDatumContent;
+
+typedef struct PartitionBoundInfoData
+{
+	char		strategy;		/* list or range bounds? */
+	int			ndatums;		/* Length of the datums following array */
+	Datum	  **datums;			/* Array of datum-tuples with key->partnatts
+								 * datums each */
+	RangeDatumContent **content;	/* what's contained in each range bound
+									 * datum? (see the above enum); NULL for
+									 * list partitioned tables */
+	int		   *indexes;		/* Partition indexes; one entry per member of
+								 * the datums array (plus one if range
+								 * partitioned table) */
+	bool		has_null;		/* Is there a null-accepting partition? false
+								 * for range partitioned tables */
+	int			null_index;		/* Index of the null-accepting partition; -1
+								 * for range partitioned tables */
+} PartitionBoundInfoData;
+
+/*
+ * When qsort'ing partition bounds after reading from the catalog, each bound
+ * is represented with one of the following structs.
+ */
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	int		index;
+	Datum	value;
+} PartitionListValue;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;		/* range bound datums */
+	RangeDatumContent *content;	/* what's contained in each datum?*/
+	bool	lower;		/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+							   void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+						   void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col,
+					   StrategyNumber strategy, bool *need_relabel);
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+					 List *datums, bool lower);
+static int32 partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2);
+
+static int32 partition_bound_cmp(PartitionKey key,
+					PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound);
+static int partition_bound_bsearch(PartitionKey key,
+						PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	int		ndatums = 0;
+
+	/* List partitioning specific */
+	PartitionListValue **all_values = NULL;
+	bool	found_null = false;
+	int		null_index = -1;
+
+	/* Range partitioning specific */
+	PartitionRangeBound **rbounds = NULL;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, inhrelid);
+
+		/*
+		 * It is possible that the pg_class tuple of a partition has not been
+		 * updated yet to set its relpartbound field.  The only case where
+		 * this happens is when we open the parent relation to check using its
+		 * partition descriptor that a new partition's bound does not overlap
+		 * some existing partition.
+		 */
+		if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+
+		datum = SysCacheGetAttr(RELOID, tuple,
+								Anum_pg_class_relpartbound,
+								&isnull);
+		Assert(!isnull);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		partoids = lappend_oid(partoids, inhrelid);
+		ReleaseSysCache(tuple);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null = false;
+				null_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					if (spec->strategy != PARTITION_STRATEGY_LIST)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null)
+								elog(ERROR, "found null more than once");
+							found_null = true;
+							null_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				ndatums = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **) palloc(ndatums *
+											sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp, (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, k;
+				PartitionRangeBound **all_bounds,
+									 *prev;
+				bool   *distinct_indexes;
+
+				all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+											sizeof(PartitionRangeBound *));
+				distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
+
+				/*
+				 * Create a unified list of range bounds across all the
+				 * partitions.
+				 */
+				i = j = 0;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					if (spec->strategy != PARTITION_STRATEGY_RANGE)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					lower = make_one_range_bound(key, i, spec->lowerdatums,
+												 true);
+					upper = make_one_range_bound(key, i, spec->upperdatums,
+												 false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				ndatums = 0;
+				prev = NULL;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+					bool	is_distinct = false;
+					int		j;
+
+					/* Is current bound is distinct from the previous? */
+					for (j = 0; j < key->partnatts; j++)
+					{
+						Datum	cmpval;
+
+						if (prev == NULL)
+						{
+							is_distinct = true;
+							break;
+						}
+
+						/*
+						 * If either of them has infinite element, we can't
+						 * equate them.  Even when both are infinite, they'd
+						 * have opposite signs, because only one of cur and
+						 * prev is a lower bound).
+						 */
+						if (cur->content[j] != RANGE_DATUM_FINITE ||
+							prev->content[j] != RANGE_DATUM_FINITE)
+						{
+							is_distinct = true;
+							break;
+						}
+						cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+												   key->partcollation[j],
+												   cur->datums[j],
+												   prev->datums[j]);
+						if (DatumGetInt32(cmpval) != 0)
+						{
+							is_distinct = true;
+							break;
+						}
+					}
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.  Also, store if the index i contains
+					 * a distinct bound that we'd like put in the relcache
+					 * array.
+					 */
+					if (is_distinct)
+					{
+						distinct_indexes[i] = true;
+						ndatums++;
+					}
+					else
+						distinct_indexes[i] = false;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				rbounds = (PartitionRangeBound **) palloc(ndatums *
+											sizeof(PartitionRangeBound *));
+				k = 0;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					if (distinct_indexes[i])
+						rbounds[k++] = all_bounds[i];
+				}
+				Assert(k == ndatums);
+				break;
+			}
+
+			default:
+				elog(ERROR, "unexpected partition strategy: %d",
+							(int) key->strategy);
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		PartitionBoundInfo boundinfo;
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+		boundinfo = (PartitionBoundInfoData *)
+									palloc0(sizeof(PartitionBoundInfoData));
+		boundinfo->strategy = key->strategy;
+		boundinfo->ndatums = ndatums;
+		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				boundinfo->has_null = found_null;
+				boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
+
+				/*
+				 * Copy values.  Indexes of individual values are mapped to
+				 * canonical values so that they match for any two list
+				 * partitioned tables with same number of partitions and same
+				 * lists per partition.  One way to canonicalize is to assign
+				 * the index in all_values[] of the smallest value of each
+				 * partition, as the index of all of the partition's values.
+				 */
+				for (i = 0; i < ndatums; i++)
+				{
+					boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
+					boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If the old index has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					boundinfo->indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * If null-accepting partition has no mapped index yet, assign
+				 * one.  This could happen if such partition accepts only null
+				 * and hence not covered in the above loop which only handled
+				 * non-null values.
+				 */
+				if (found_null)
+				{
+					Assert(null_index >= 0);
+					if (mapping[null_index] == -1)
+						mapping[null_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null)
+					boundinfo->null_index = mapping[null_index];
+				else
+					boundinfo->null_index = -1;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				boundinfo->content = (RangeDatumContent **) palloc(ndatums *
+												 sizeof(RangeDatumContent *));
+				boundinfo->indexes = (int *) palloc((ndatums+1) *
+													sizeof(int));
+
+				for (i = 0; i < ndatums; i++)
+				{
+					int		j;
+
+					boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
+															sizeof(Datum));
+					boundinfo->content[i] = (RangeDatumContent *)
+												palloc(key->partnatts *
+												   sizeof(RangeDatumContent));
+					for (j = 0; j < key->partnatts; j++)
+					{
+						if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
+							boundinfo->datums[i][j] =
+											datumCopy(rbounds[i]->datums[j],
+													  key->parttypbyval[j],
+													  key->parttyplen[j]);
+						/* Remember, we are storing the tri-state value. */
+						boundinfo->content[i][j] = rbounds[i]->content[j];
+					}
+
+					/*
+					 * There is no mapping for invalid indexes.
+					 *
+					 * Any lower bounds in the rbounds array have invalid
+					 * indexes assigned, because the values between the
+					 * previous bound (if there is one) and this (lower)
+					 * bound are not part of the range of any existing
+					 * partition.
+					 */
+					if (rbounds[i]->lower)
+						boundinfo->indexes[i] = -1;
+					else
+					{
+						int		orig_index = rbounds[i]->index;
+
+						/* If the old index is has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						boundinfo->indexes[i] = mapping[orig_index];
+					}
+				}
+				boundinfo->indexes[i] = -1;
+				break;
+			}
+
+			default:
+				elog(ERROR, "unexpected partition strategy: %d",
+							(int) key->strategy);
+		}
+
+		result->boundinfo = boundinfo;
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because PartitionBoundInfo is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo b1, PartitionBoundInfo b2)
+{
+	int		i;
+
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	if (b1->ndatums != b2->ndatums)
+		return false;
+
+	if (b1->has_null != b2->has_null)
+		return false;
+
+	if (b1->null_index != b2->null_index)
+		return false;
+
+	for (i = 0; i < b1->ndatums; i++)
+	{
+		int		j;
+
+		for (j = 0; j < key->partnatts; j++)
+		{
+			int32	cmpval;
+
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[j],
+													 key->partcollation[j],
+													 b1->datums[i][j],
+													 b2->datums[i][j]));
+			if (cmpval != 0)
+				return false;
+
+			/* Range partitions can have infinite datums */
+			if (b1->content != NULL && b1->content[i][j] != b2->content[i][j])
+				return false;
+		}
+
+		if(b1->indexes[i] != b2->indexes[i])
+			return false;
+	}
+
+	/* There are ndatums+1 indexes in case of range partitions */
+	if (key->strategy == PARTITION_STRATEGY_RANGE &&
+		b1->indexes[i] != b2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				ListCell   *cell;
+
+				Assert(boundinfo &&
+					   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
+					   (boundinfo->ndatums > 0 || boundinfo->has_null));
+
+				foreach (cell, spec->listdatums)
+				{
+					Const  *val = lfirst(cell);
+
+					if (!val->constisnull)
+					{
+						int		offset;
+						bool	equal;
+
+						offset = partition_bound_bsearch(key, boundinfo,
+														 &val->constvalue,
+														 true, &equal);
+						if (offset >= 0 && equal)
+						{
+							overlap = true;
+							with = boundinfo->indexes[offset];
+							break;
+						}
+					}
+					else if (boundinfo->has_null)
+					{
+						overlap = true;
+						with = boundinfo->null_index;
+						break;
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+			upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified lower and upper bounds
+			 */
+			if (partition_rbound_cmp(key, lower->datums, lower->content, true,
+									 upper) >= 0)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (partdesc->nparts > 0)
+			{
+				PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+				int		  off1, off2;
+				bool	  equal = false;
+
+				Assert(boundinfo && boundinfo->ndatums > 0 &&
+					   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than or equal with the new lower bound.
+				 */
+				off1 = partition_bound_bsearch(key, boundinfo, lower, true,
+											   &equal);
+
+				/*
+				 * If equal has been set to true, that means the new lower
+				 * bound is found to be equal with the bound at off1, which
+				 * clearly means an overlap with the partition at index
+				 * off1+1).
+				 *
+				 * Otherwise, check if there is a "gap" that could be occupied
+				 * by the new partition.  In case of a gap, the new upper
+				 * bound should not cross past the upper boundary of the gap,
+				 * that is, off2 == off1 should be true.
+				 */
+				if (!equal && boundinfo->indexes[off1+1] < 0)
+				{
+					off2 = partition_bound_bsearch(key, boundinfo, upper,
+												   true, &equal);
+
+					if (equal || off1 != off2)
+					{
+						overlap = true;
+						with = boundinfo->indexes[off2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					with = boundinfo->indexes[off1+1];
+				}
+			}
+
+			break;
+		}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) key->strategy);
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) key->strategy);
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference here */
+	if (found_whole_row)
+		elog(ERROR, "unexpected whole-row reference found in partition key");
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *keyCol;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		keyCol = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		keyCol = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * We must remove any NULL value in the list; we handle it separately
+	 * below.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/*
+		 * Gin up a col IS NOT NULL test that will be AND'd with other
+		 * expressions
+		 */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) keyCol;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/*
+		 * Gin up a col IS NULL test that will be OR'd with other expressions
+		 */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) keyCol;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr containing this partition's values */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	opexpr = makeNode(ScalarArrayOpExpr);
+	opexpr->opno = operoid;
+	opexpr->opfuncid = get_opcode(operoid);
+	opexpr->useOr = true;
+	opexpr->inputcollid = key->partcollation[0];
+	opexpr->args = list_make2(keyCol, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key, emitting an OpExpr for each using
+	 * the corresponding lower and upper datums as constant operands.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *keyCol;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		Oid				operoid;
+		NullTest	   *nulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			keyCol = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			keyCol = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/*
+		 * Emit a IS NOT NULL expression for non-Var keys, because whereas
+		 * simple attributes are covered by NOT NULL constraints, expression
+		 * keys are still nullable which is not acceptable in case of range
+		 * partitioning.
+		 */
+		if (!IsA(keyCol, Var))
+		{
+			nulltest = makeNode(NullTest);
+			nulltest->arg = (Expr *) keyCol;
+			nulltest->nulltesttype = IS_NOT_NULL;
+			nulltest->argisrow = false;
+			nulltest->location = -1;
+			result = lappend(result, nulltest);
+		}
+
+		/*
+		 * Stop at this column if either of lower or upper datum is infinite,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (keyCol = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create the test expression */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* This can never be, but it's better to make sure */
+				if (i == key->partnatts - 1)
+					elog(ERROR, "invalid range bound specification");
+
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) keyCol,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val != upper_val.  Emit expressions
+		 * (keyCol >= lower_val) and (keyCol < upper_val), then stop.
+		 */
+		if (lower_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTGreaterEqualStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) keyCol,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTLessStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				keyCol = (Node *) makeRelabelType((Expr *) keyCol,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) keyCol,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * opreator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	if (!rel->rd_rel->relispartition)	/* should not happen */
+		elog(ERROR, "relation \"%s\" has relispartition = false",
+					RelationGetRelationName(rel));
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	if (isnull)		/* should not happen */
+		elog(ERROR, "relation \"%s\" has relpartbound = null",
+					RelationGetRelationName(rel));
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list partition bound datums
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			val1 = (*(const PartitionListValue **) a)->value,
+					val2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * make_one_range_bound
+ *
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.  Made into a function
+ * because there are multiple sites that want to use this facility.
+ */
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->content = (RangeDatumContent *) palloc0(key->partnatts *
+												sizeof(RangeDatumContent));
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		/* What's contained in this range datum? */
+		bound->content[i] = !datum->infinite
+								? RANGE_DATUM_FINITE
+								: (lower ? RANGE_DATUM_NEG_INF
+										 : RANGE_DATUM_POS_INF);
+
+		if (bound->content[i] == RANGE_DATUM_FINITE)
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
+}
+
+/*
+ * partition_rbound_cmp
+ * 
+ * Return for two range bounds whether the 1st one (specified in datum1,
+ * content1, and lower1) is <=, =, >= the bound specified in *b2
+ */
+static int32
+partition_rbound_cmp(PartitionKey key,
+					 Datum *datums1, RangeDatumContent *content1, bool lower1,
+					 PartitionRangeBound *b2)
+{
+	int32	cmpval;
+	int		i;
+	Datum  *datums2 = b2->datums;
+	RangeDatumContent *content2 = b2->content;
+	bool	lower2 = b2->lower;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (content1[i] != RANGE_DATUM_FINITE &&
+			content2[i] != RANGE_DATUM_FINITE)
+			/*
+			 * Both are infinity, so they are equal unless one is
+			 * negative infinity and other positive (or vice versa)
+			 */
+			return content1[i] == content2[i] ? 0
+								: (content1[i] < content2[i] ? -1 : 1);
+		else if (content1[i] != RANGE_DATUM_FINITE)
+			return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+		else if (content2[i] != RANGE_DATUM_FINITE)
+			return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i],
+												 datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If
+	 * they compare equal though, we still have to consider whether
+	 * the boundaries are inclusive or exclusive.  Exclusive one is
+	 * considered smaller of the two.
+	 */
+	if (cmpval == 0 && lower1 != lower2)
+		cmpval = lower1 ? 1 : -1;
+
+	return cmpval;
+}
+
+/*
+ * partition_bound_cmp
+ * 
+ * Return whether the bound at offset in boundinfo is <=, =, >= the argument
+ * specified in *probe.
+ */
+static int32
+partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+					int offset, void *probe, bool probe_is_bound)
+{
+	Datum  *bound_datums = boundinfo->datums[offset];
+	int32	cmpval = -1;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   bound_datums[0],
+										   *(Datum *) probe));
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			RangeDatumContent  *content = boundinfo->content[offset];
+
+			if (probe_is_bound)
+			{
+				/*
+				 * We need to pass whether the existing bound is a lower
+				 * bound, so that two equal-valued lower and upper bounds are
+				 * not regarded equal.
+				 */
+				bool	lower = boundinfo->indexes[offset] < 0;
+
+				cmpval = partition_rbound_cmp(key,
+											  bound_datums, content, lower,
+											  (PartitionRangeBound *) probe);
+			}
+
+			break;
+		}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d",
+						(int) key->strategy);
+	}
+
+	return cmpval;
+}
+
+/*
+ * Binary search on a collection of partition bounds. Returns greatest index
+ * of bound in array boundinfo->datums which is less or equal with *probe.
+ * If all bounds in the array are greater than *probe, -1 is returned.
+ *
+ * *probe could either be a partition bound or a Datum array representing
+ * the partition key of a tuple being routed; probe_is_bound tells which.
+ * We pass that down to the comparison function so that it can interpret the
+ * contents of *probe accordingly.
+ *
+ * *is_equal is set to whether the bound at the returned index is equal with
+ * *probe.
+ */
+static int
+partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+						void *probe, bool probe_is_bound, bool *is_equal)
+{
+	int		lo,
+			hi,
+			mid;
+
+	lo = -1;
+	hi = boundinfo->ndatums - 1;
+	while (lo < hi)
+	{
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cmpval = partition_bound_cmp(key, boundinfo, mid, probe,
+									 probe_is_bound);
+		if (cmpval <= 0)
+		{
+			lo = mid;
+			*is_equal = (cmpval == 0);
+		}
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6affcc..d6d52d9929 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 1ab9030a3c..d953b4408b 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 826c47685b..8a803233ca 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -65,6 +66,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
 	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;		/* if above is true */
+	List	   *partition_constraint; /* for attach partition validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
+							 stmt->partbound != NULL,
 							 &inheritOids, &old_constraints, &parentOidCount);
 
 	/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	descriptor = BuildDescForRelation(schema);
 
 	/*
-	 * Notice that we allow OIDs here only for plain tables, even though some
-	 * other relkinds can support them.  This is necessary because the
-	 * default_with_oids GUC must apply only to plain tables and not any other
-	 * relkind; doing otherwise would break existing pg_dump files.  We could
-	 * allow explicit "WITH OIDS" while not allowing default_with_oids to
-	 * affect other relkinds, but it would complicate interpretOidsOption().
+	 * Notice that we allow OIDs here only for plain tables and partitioned
+	 * tables, even though some other relkinds can support them.  This is
+	 * necessary because the default_with_oids GUC must apply only to plain
+	 * tables and not any other relkind; doing otherwise would break existing
+	 * pg_dump files.  We could allow explicit "WITH OIDS" while not allowing
+	 * default_with_oids to affect other relkinds, but it would complicate
+	 * interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
 									   (relkind == RELKIND_RELATION ||
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parent;
+
+		/* Already have strong enough lock on the parent */
+		parent = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partition key of parentRel, so it better have one.
+		 */
+		if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parent))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parent, stmt->partbound);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parent, bound);
+		heap_close(parent, NoLock);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partition key information into the catalog.
@@ -1146,6 +1219,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1452,6 +1529,7 @@ storage_name(char c)
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of names (as RangeVar nodes) of parent relations.
  * 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
  *
  * Output arguments:
  * 'supOids' receives a list of the OIDs of the parent relations.
@@ -1503,7 +1581,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1513,6 +1592,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema = NIL;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1532,6 +1612,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 						MaxHeapAttributeNumber)));
 
 	/*
+	 * In case of a partition, there are no new column definitions, only
+	 * dummy ColumnDefs created for column constraints.  We merge these
+	 * constraints inherited from the parent.
+	 */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
+	/*
 	 * Check for duplicate names in the explicit list of attributes.
 	 *
 	 * Although we might consider merging such entries in the same way that we
@@ -1611,18 +1702,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * We do not allow partitioned tables and partitions to participate
+		 * in regular inheritance.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
 
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1632,7 +1740,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create a permanent relation as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1640,7 +1750,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1877,9 +1989,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		pfree(newattno);
 
 		/*
-		 * Close the parent rel, but keep our ShareUpdateExclusiveLock on it
-		 * until xact commit.  That will prevent someone else from deleting or
-		 * ALTERing the parent before the child is committed.
+		 * Close the parent rel, but keep our lock on it until xact commit.
+		 * That will prevent someone else from deleting or ALTERing the parent
+		 * before the child is committed.
 		 */
 		heap_close(relation, NoLock);
 	}
@@ -1887,7 +1999,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	/*
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.
+	 * columns into the inherited schema list.  Although, we never have any
+	 * explicitly declared columns if the table is a partition.
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1916,6 +2029,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
+				 * Partitions have only one parent, so conflict should never
+				 * occur
+				 */
+				Assert(!is_partition);
+
+				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
 				 */
@@ -1997,6 +2116,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have the column definition list for a partition, we can
+	 * check whether the columns referenced in column option specifications
+	 * actually exist.  Also, we merge the options into the corresponding
+	 * column definitions.
+	 */
+	if (is_partition && list_length(saved_schema) > 0)
+	{
+		schema = list_concat(schema, saved_schema);
+
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
+
+			/*
+			 * Partition column option that does not belong to a column from
+			 * the parent.  This works because the columns from the parent
+			 * come first in the list (see above).
+			 */
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" does not exist",
+							coldef->colname)));
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);		/* need to save it in case
+													 * we delete it */
+
+				if (strcmp(coldef->colname, restdef->colname) == 0)
+				{
+					/*
+					 * merge the column options into the column from the
+					 * parent
+					 */
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					list_delete_cell(schema, rest, prev);
+				}
+				prev = rest;
+				rest = next;
+			}
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -3158,6 +3327,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);
@@ -3269,12 +3443,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3475,6 +3651,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);
@@ -3545,7 +3727,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, we did
+		 * not modify anything about it that will change its toasting
+		 * requirement, so no need to check.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  tab->partition_constraint == NIL) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3794,6 +3983,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);
@@ -3979,7 +4174,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			if (tab->constraints != NIL || tab->new_notnull)
+			if (tab->constraints != NIL || tab->new_notnull ||
+				tab->partition_constraint != NIL)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4059,6 +4255,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4123,6 +4320,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_constraint)
+	{
+		needscan = true;
+		partqualstate = (List *)
+						ExecPrepareExpr((Expr *) tab->partition_constraint,
+										estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4312,6 +4518,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4509,7 +4720,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4831,6 +5043,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5277,6 +5494,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5352,6 +5583,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * If the table is a range partitioned table, check that the column
 	 * is not in the partition key.
@@ -5406,6 +5654,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5965,6 +6228,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7985,6 +8257,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10286,6 +10568,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10298,12 +10585,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;
 
@@ -10348,37 +10630,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				 errmsg("cannot inherit from partitioned table \"%s\"",
 						parent->relname)));
 
-	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10413,6 +10669,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);
 
@@ -10427,16 +10746,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;
 }
 
 /*
@@ -10487,7 +10798,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
@@ -10505,12 +10816,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		child_is_partition = false;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10558,6 +10874,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (child_is_partition)
+			{
+				Assert(childatt->attinhcount == 1);
+				childatt->attislocal = false;
+			}
+
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10580,7 +10908,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.
@@ -10599,10 +10927,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		child_is_partition = false;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10679,6 +11012,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (child_is_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10703,6 +11048,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10716,13 +11101,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];
@@ -10731,19 +11114,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		child_is_partition = false;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10753,7 +11128,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);
 
@@ -10774,11 +11149,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	if (!found)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_TABLE),
-				 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-						RelationGetRelationName(parent_rel),
-						RelationGetRelationName(rel))));
+	{
+		if (child_is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+							RelationGetRelationName(child_rel),
+							RelationGetRelationName(parent_rel))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+							RelationGetRelationName(parent_rel),
+							RelationGetRelationName(child_rel))));
+	}
 
 	/*
 	 * Search through child columns looking for ones matching parent rel
@@ -10787,7 +11171,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)))
@@ -10849,7 +11233,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);
 
@@ -10880,7 +11264,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)
@@ -10892,30 +11276,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;
 }
 
 /*
@@ -12600,3 +12974,454 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	Relation	attachRel,
+				catalog;
+	List	   *childrels;
+	TupleConstr	*attachRel_constr;
+	List	   *partConstraint,
+			   *existConstraint;
+	SysScanDesc scan;
+	ScanKeyData skey;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	bool		skip_validate = false;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * Table being attached should not already be part of inheritance; either
+	 * as a child table...
+	 */
+	catalog = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or as a parent table (except the case when it is partitioned) */
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+							  1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(catalog, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* Temp parent cannot have a partition that is itself not a temp */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		attachRel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
+						RelationGetRelationName(rel))));
+
+	/* If the parent is temp, it must belong to this session */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!rel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach as partition of temporary relation of another session")));
+
+	/* Ditto for the partition */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		 errmsg("cannot attach temporary relation of another session as partition")));
+
+	/* If parent has OIDs then child must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Check that the new partition's bound is valid and does not overlap any
+	 * of existing partitions of the parent - note that it does not return
+	 * on error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel), rel,
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * Generate partition constraint from the partition bound specification.
+	 * If the parent itself is a partition, make sure to include its
+	 * constraint as well.
+	 */
+	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+														 cmd->bound),
+								 RelationGetPartitionQual(rel, true));
+	partConstraint = (List *) eval_const_expressions(NULL,
+													 (Node *) partConstraint);
+	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+	partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+	/*
+	 * Check if we can do away with having to scan the table being attached
+	 * to validate the partition constraint, by *proving* that the existing
+	 * constraints of the table *imply* the partition predicate.  We include
+	 * the table's check constraints and NOT NULL constraints in the list of
+	 * clauses passed to predicate_implied_by().
+	 *
+	 * There is a case in which we cannot rely on just the result of the
+	 * proof.
+	 */
+	tupleDesc = RelationGetDescr(attachRel);
+	attachRel_constr = tupleDesc->constr;
+	existConstraint = NIL;
+	if (attachRel_constr > 0)
+	{
+		int			num_check = attachRel_constr->num_check;
+		int			i;
+		Bitmapset  *not_null_attrs = NULL;
+		List	   *part_constr;
+		ListCell   *lc;
+		bool		partition_accepts_null = true;
+		int			partnatts;
+
+		if (attachRel_constr->has_not_null)
+		{
+			int			natts = attachRel->rd_att->natts;
+
+			for (i = 1; i <= natts; i++)
+			{
+				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+				if (att->attnotnull && !att->attisdropped)
+				{
+					NullTest   *ntest = makeNode(NullTest);
+
+					ntest->arg = (Expr *) makeVar(1,
+												  i,
+												  att->atttypid,
+												  att->atttypmod,
+												  att->attcollation,
+												  0);
+					ntest->nulltesttype = IS_NOT_NULL;
+
+					/*
+					 * argisrow=false is correct even for a composite column,
+					 * because attnotnull does not represent a SQL-spec IS NOT
+					 * NULL test in such a case, just IS DISTINCT FROM NULL.
+					 */
+					ntest->argisrow = false;
+					ntest->location = -1;
+					existConstraint = lappend(existConstraint, ntest);
+					not_null_attrs = bms_add_member(not_null_attrs, i);
+				}
+			}
+		}
+
+		for (i = 0; i < num_check; i++)
+		{
+			Node	   *cexpr;
+
+			/*
+			 * If this constraint hasn't been fully validated yet, we must
+			 * ignore it here.
+			 */
+			if (!attachRel_constr->check[i].ccvalid)
+				continue;
+
+			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+			/*
+			 * Run each expression through const-simplification and
+			 * canonicalization.  It is necessary, because we will be
+			 * comparing it to similarly-processed qual clauses, and may fail
+			 * to detect valid matches without this.
+			 */
+			cexpr = eval_const_expressions(NULL, cexpr);
+			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+			existConstraint = list_concat(existConstraint,
+										  make_ands_implicit((Expr *) cexpr));
+		}
+
+		existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+		/* And away we go ... */
+		if (predicate_implied_by(partConstraint, existConstraint))
+			skip_validate = true;
+
+		/*
+		 * We choose to err on the safer side, ie, give up on skipping the
+		 * the validation scan, if the partition key column doesn't have
+		 * the NOT NULL constraint and the table is to become a list partition
+		 * that does not accept nulls.  In this case, the partition predicate
+		 * (partConstraint) does include an 'key IS NOT NULL' expression,
+		 * however, because of the way predicate_implied_by_simple_clause()
+		 * is designed to handle IS NOT NULL predicates in the absence of a
+		 * IS NOT NULL clause, we cannot rely on just the above proof.
+		 *
+		 * That is not an issue in case of a range partition, because if there
+		 * were no NOT NULL constraint defined on the key columns, an error
+		 * would be thrown before we get here anyway.  That is not true,
+		 * however, if any of the partition keys is an expression, which is
+		 * handled below.
+		 */
+		part_constr = linitial(partConstraint);
+		part_constr = make_ands_implicit((Expr *) part_constr);
+
+		/*
+		 * part_constr contains an IS NOT NULL expression, if this is a list
+		 * partition that does not accept nulls (in fact, also if this is a
+		 * range partition and some partition key is an expression, but we
+		 * never skip validation in that case anyway; see below)
+		 */
+		foreach(lc, part_constr)
+		{
+			Node *expr = lfirst(lc);
+
+			if (IsA(expr, NullTest) &&
+				((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
+			{
+				partition_accepts_null = false;
+				break;
+			}
+		}
+
+		partnatts = get_partition_natts(key);
+		for (i = 0; i < partnatts; i++)
+		{
+			AttrNumber	partattno;
+
+			partattno = get_partition_col_attnum(key, i);
+
+			/* If partition key is an expression, must not skip validation */
+			if (!partition_accepts_null &&
+				(partattno == 0 ||
+				 !bms_is_member(partattno, not_null_attrs)))
+				skip_validate = false;
+		}
+	}
+
+	if (skip_validate)
+		elog(NOTICE, "skipping scan to validate partition constraint");
+
+	/*
+	 * Set up to have the table to be scanned to validate the partition
+	 * constraint (see partConstraint above).  If it's a partitioned table,
+	 * we instead schdule its leaf partitions to be scanned instead.
+	 */
+	if (!skip_validate)
+	{
+		List	   *all_parts;
+		ListCell   *lc;
+
+		/* Take an exclusive lock on the partitions to be checked */
+		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+											 AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+		foreach(lc, all_parts)
+		{
+			AlteredTableInfo *tab;
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+
+			/* Lock already taken */
+			if (part_relid != RelationGetRelid(attachRel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = attachRel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel != attachRel &&
+				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+
+			/* Grab a work queue entry */
+			tab = ATGetQueueEntry(wqueue, part_rel);
+
+			constr = linitial(partConstraint);
+			tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+			/* keep our lock until commit */
+			if (part_rel != attachRel)
+				heap_close(part_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that the new partition is now included
+	 * in rel's partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a584..5e3989acd2 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a81096f..c6b0e4f2b3 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5d1a1d46fa..e30c57e86b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4216,6 +4217,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5139,6 +5177,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3c3159851f..b7a109cfb0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2669,6 +2670,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3431,6 +3463,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 399744193c..973fb152c1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5081..0d858f5920 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8a65..c587d4e1d7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_NODE_FIELD(lowerdatums);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bbf5fba357..2ed7b5259d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -278,7 +279,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	add_drop opt_asc_desc opt_nulls_order
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
-	   replica_identity
+	   replica_identity partition_cmd
 %type <list>	alter_table_cmds alter_type_cmds
 
 %type <dbehavior>	opt_drop_behavior
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -576,7 +584,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 /* ordinary key words in alphabetical order */
 %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
-	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
 	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
 	BOOLEAN_P BOTH BY
@@ -592,7 +600,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
-	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+	DOUBLE_P DROP
 
 	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -1793,6 +1802,24 @@ AlterTableStmt:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
+		|	ALTER TABLE relation_expr partition_cmd
+				{
+					AlterTableStmt *n = makeNode(AlterTableStmt);
+					n->relation = $3;
+					n->cmds = list_make1($4);
+					n->relkind = OBJECT_TABLE;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+		|	ALTER TABLE IF_P EXISTS relation_expr partition_cmd
+				{
+					AlterTableStmt *n = makeNode(AlterTableStmt);
+					n->relation = $5;
+					n->cmds = list_make1($6);
+					n->relkind = OBJECT_TABLE;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 		|	ALTER TABLE ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
 				{
 					AlterTableMoveAllStmt *n =
@@ -1938,6 +1965,34 @@ alter_table_cmds:
 			| alter_table_cmds ',' alter_table_cmd	{ $$ = lappend($1, $3); }
 		;
 
+partition_cmd:
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			ATTACH PARTITION qualified_name ForValues
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+		;
+
 alter_table_cmd:
 			/* ALTER TABLE <name> ADD <coldef> */
 			ADD_P columnDef
@@ -2473,6 +2528,73 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerdatums = $5;
+					n->upperdatums = $9;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2890,6 +3012,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionSpec OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partspec = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionSpec
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2935,6 +3095,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2957,6 +3122,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2968,6 +3144,28 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+		TableConstraint					{ $$ = $1; }
+		|	ColId ColQualList
+			{
+				ColumnDef *n = makeNode(ColumnDef);
+				n->colname = $1;
+				n->typeName = NULL;
+				n->inhcount = 0;
+				n->is_local = true;
+				n->is_not_null = false;
+				n->is_from_type = false;
+				n->storage = 0;
+				n->raw_default = NULL;
+				n->cooked_default = NULL;
+				n->collOid = InvalidOid;
+				SplitColQualList($2, &n->constraints, &n->collClause,
+								 yyscanner);
+				n->location = @1;
+				$$ = (Node *) n;
+			}
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4753,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;
+				}
 		;
 
 /*****************************************************************************
@@ -13774,6 +14014,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13820,6 +14061,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index fc896a27fe..5a58398cd6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2581,6 +2586,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2663,6 +2669,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3027,3 +3046,245 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partition key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			if (spec->strategy != PARTITION_STRATEGY_LIST)
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			result_spec->listdatums = NIL;
+			foreach(cell, spec->listdatums)
+			{
+				A_Const    *con = (A_Const *) lfirst(cell);
+				Node	   *value;
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+							 format_type_be(get_partition_col_typid(key, 0)),
+											colname),
+							parser_errposition(pstate,
+											   exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("FROM must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("TO must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+									*rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, i),
+												  COERCION_ASSIGNMENT,
+												  COERCE_IMPLICIT_CAST,
+												  -1);
+					if (value == NULL)
+						ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+
+		default:
+			elog(ERROR, "unexpected partition strategy: %d", (int) strategy);
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce408ae..fd4eff4907 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7f3ba74db2..ae77d15914 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2);
 
 
 /*
@@ -1158,6 +1161,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
+					PartitionDesc partdesc2)
+{
+	int		i;
+
+	if (partdesc1 != NULL)
+	{
+		if (partdesc2 == NULL)
+			return false;
+		if (partdesc1->nparts != partdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || partdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < partdesc1->nparts; i++)
+		{
+			if (partdesc1->oids[i] != partdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is private to partition.c.
+		 */
+		if (partdesc1->boundinfo != NULL)
+		{
+			if (partdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key, partdesc1->boundinfo,
+											 partdesc2->boundinfo))
+				return false;
+		}
+		else if (partdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (partdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1285,13 +1340,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2288,6 +2348,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2436,11 +2500,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, partition key, and partition descriptor substructures
+		 * in place, because various places assume that these structures won't
+		 * move while they are working with an open relcache entry.  (Note:
+		 * the refcount	mechanism for tupledescs might someday allow us to
+		 * remove this hack for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2451,6 +2516,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);
@@ -2481,6 +2547,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
@@ -2535,6 +2604,15 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+		/* partition key must be preserved */
+		SWAPFIELD(PartitionKey, rd_partkey);
+		SWAPFIELD(MemoryContext, rd_partkeycxt);
+		/* preserve old partdesc if no logical change */
+		if (keep_partdesc)
+		{
+			SWAPFIELD(PartitionDesc, rd_partdesc);
+			SWAPFIELD(MemoryContext, rd_pdcxt);
+		}
 
 #undef SWAPFIELD
 
@@ -3770,6 +3848,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5298,6 +5379,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a93e8..77dc1983e8 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000000..70d8325137
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * PartitionBoundInfo encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct PartitionBoundInfoData *PartitionBoundInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	PartitionBoundInfo	boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   PartitionBoundInfo p1, PartitionBoundInfo p2);
+
+extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 6a86c93efb..a61b7a2917 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4df5..fa48f2e960 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412cae3..c514d3fc93 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 01c2f93a6f..6b95c48447 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/*
+	 * Range partition lower and upper bounds; each member of the lists
+	 * is a PartitionRangeDatum (see below).
+	 */
+	List	   *lowerdatums;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1577,7 +1622,9 @@ typedef enum AlterTableType
 	AT_DisableRowSecurity,		/* DISABLE ROW SECURITY */
 	AT_ForceRowSecurity,		/* FORCE ROW SECURITY */
 	AT_NoForceRowSecurity,		/* NO FORCE ROW SECURITY */
-	AT_GenericOptions			/* OPTIONS (...) */
+	AT_GenericOptions,			/* OPTIONS (...) */
+	AT_AttachPartition,			/* ATTACH PARTITION */
+	AT_DetachPartition			/* DETACH PARTITION */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873beca..581ff6eedb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f70c1..783bb0009f 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de37f1..cd7ea1d2dd 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index df6fe13c3a..09cc193f2f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3020,3 +3020,300 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+--
+-- ATTACH PARTITION
+--
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check any TEMP-ness
+CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE perm_part (a int);
+ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
+ERROR:  cannot attach a permanent relation as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted, perm_part;
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+NOTICE:  skipping scan to validate partition constraint
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+NOTICE:  skipping scan to validate partition constraint
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ERROR:  partition constraint is violated by some row
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+NOTICE:  skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR:  "part_2" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted2" is already a child of "list_parted2".
+--
+-- DETACH PARTITION
+--
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR:  relation "part_1" is not a partition of relation "list_parted2"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+DROP TABLE part_3_4;
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR:  cannot drop inherited constraint "check_a2" of relation "part_2"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR:  cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR:  cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR:  cannot change inheritance of a partition
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part_2
+drop cascades to table part_5
+drop cascades to table part_5_a
+drop cascades to table part_1
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 02e0720eec..c3afca687f 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -439,3 +439,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partition key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR:  TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR:  cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create a permanent relation as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR:  partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ec61b02c57..c4ed69304f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1907,3 +1907,265 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+--
+-- ATTACH PARTITION
+--
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check any TEMP-ness
+CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
+CREATE TABLE perm_part (a int);
+ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
+DROP TABLE temp_parted, perm_part;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int NOT NULL CHECK (a = 1),
+	b int NOT NULL CHECK (b >= 10 AND b < 18)
+);
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+DROP TABLE part_3_4;
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ALTER TABLE part_2 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ALTER TABLE part_2 INHERIT inh_test;
+
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2af3214b19..7818bc01f6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
2.11.0

0004-psql-and-pg_dump-support-for-partitions-20.patchtext/x-diff; name=0004-psql-and-pg_dump-support-for-partitions-20.patchDownload
From 30c4f5e95839e81f1ace27cefdb55b07b24d8033 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/7] psql and pg_dump support for partitions.

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |  82 ++++++++++++++++++++
 src/bin/pg_dump/common.c                   |  86 +++++++++++++++++++++
 src/bin/pg_dump/pg_dump.c                  | 118 +++++++++++++++++++++++++++--
 src/bin/pg_dump/pg_dump.h                  |  12 +++
 src/bin/psql/describe.c                    |  85 ++++++++++++++++++---
 src/test/regress/expected/create_table.out |  40 ++++++++++
 src/test/regress/sql/create_table.sql      |  12 +++
 7 files changed, 415 insertions(+), 20 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60fe794816..4e2ba19d1b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8450,6 +8450,88 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_PartitionBoundSpec:
+			{
+				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				ListCell *cell;
+				char	 *sep;
+
+				switch (spec->strategy)
+				{
+					case PARTITION_STRATEGY_LIST:
+						Assert(spec->listdatums != NIL);
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " IN (");
+						sep = "";
+						foreach (cell, spec->listdatums)
+						{
+							Const *val = lfirst(cell);
+
+							appendStringInfoString(buf, sep);
+							get_const_expr(val, context, -1);
+							sep = ", ";
+						}
+
+						appendStringInfoString(buf, ")");
+						break;
+
+					case PARTITION_STRATEGY_RANGE:
+						Assert(spec->lowerdatums != NIL &&
+							   spec->upperdatums != NIL &&
+							   list_length(spec->lowerdatums) ==
+							   list_length(spec->upperdatums));
+
+						appendStringInfoString(buf, "FOR VALUES");
+						appendStringInfoString(buf, " FROM");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->lowerdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+
+						appendStringInfoString(buf, " TO");
+						appendStringInfoString(buf, " (");
+						sep = "";
+						foreach (cell, spec->upperdatums)
+						{
+							PartitionRangeDatum *datum = lfirst(cell);
+							Const *val;
+
+							appendStringInfoString(buf, sep);
+							if (datum->infinite)
+								appendStringInfoString(buf, "UNBOUNDED");
+							else
+							{
+								val = (Const *) datum->value;
+								get_const_expr(val, context, -1);
+							}
+							sep = ", ";
+						}
+						appendStringInfoString(buf, ")");
+						break;
+
+					default:
+						elog(ERROR, "unrecognized partition strategy: %d",
+							 (int) spec->strategy);
+						break;
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f028c8..22f1806eca 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int	numextmembers;
 
 static void flagInhTables(TableInfo *tbinfo, int numTables,
 			  InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
 				Size objSize);
@@ -75,6 +77,8 @@ static int	DOCatalogIdCompare(const void *p1, const void *p2);
 static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 static void findParentsByOid(TableInfo *self,
 				 InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+				 int numPartitions);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
 
 
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	NamespaceInfo *nspinfo;
 	ExtensionInfo *extinfo;
 	InhInfo    *inhinfo;
+	PartInfo    *partinfo;
 	int			numAggregates;
 	int			numInherits;
+	int			numPartitions;
 	int			numRules;
 	int			numProcLangs;
 	int			numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	inhinfo = getInherits(fout, &numInherits);
 
 	if (g_verbose)
+		write_msg(NULL, "reading partition information\n");
+	partinfo = getPartitions(fout, &numPartitions);
+
+	if (g_verbose)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "finding inheritance relationships\n");
 	flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 
+	/* Link tables to partition parents, mark parents as interesting */
+	if (g_verbose)
+		write_msg(NULL, "finding partition relationships\n");
+	flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
 	if (g_verbose)
 		write_msg(NULL, "reading column info for interesting tables\n");
 	getTableAttrs(fout, tblinfo, numTables);
@@ -323,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
 	}
 }
 
+/* flagPartitions -
+ *	 Fill in parent link fields of every target table that is partition,
+ *	 and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+			  PartInfo *partinfo, int numPartitions)
+{
+	int		i;
+
+	for (i = 0; i < numTables; i++)
+	{
+		/* Some kinds are never partitions */
+		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+			tblinfo[i].relkind == RELKIND_VIEW ||
+			tblinfo[i].relkind == RELKIND_MATVIEW)
+			continue;
+
+		/* Don't bother computing anything for non-target tables, either */
+		if (!tblinfo[i].dobj.dump)
+			continue;
+
+		/* Find the parent TableInfo and save */
+		findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+		/* Mark the parent as interesting for getTableAttrs */
+		if (tblinfo[i].partitionOf)
+		{
+			tblinfo[i].partitionOf->interesting = true;
+			addObjectDependency(&tblinfo[i].dobj,
+								tblinfo[i].partitionOf->dobj.dumpId);
+		}
+	}
+}
+
 /* flagInhAttrs -
  *	 for each dumpable table in tblinfo, flag its inherited attributes
  *
@@ -924,6 +976,40 @@ findParentsByOid(TableInfo *self,
 }
 
 /*
+ * findPartitionParentByOid
+ *	  find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+						 int numPartitions)
+{
+	Oid			oid = self->dobj.catId.oid;
+	int			i;
+
+	for (i = 0; i < numPartitions; i++)
+	{
+		if (partinfo[i].partrelid == oid)
+		{
+			TableInfo  *parent;
+
+			parent = findTableByOid(partinfo[i].partparent);
+			if (parent == NULL)
+			{
+				write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+						  partinfo[i].partparent,
+						  self->dobj.name,
+						  oid);
+				exit_nicely(1);
+			}
+			self->partitionOf = parent;
+
+			/* While we're at it, also save the partdef */
+			self->partitiondef = partinfo[i].partdef;
+		}
+	}
+}
+
+/*
  * parseOidArray
  *	  parse a string of numbers delimited by spaces into a character array
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2fb6d5dcc4..b43d152e77 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5642,9 +5642,16 @@ getInherits(Archive *fout, int *numInherits)
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
 
-	/* find all the inheritance information */
-
-	appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+	/*
+	 * Find all the inheritance information, excluding implicit inheritance
+	 * via partitioning.  We handle that case using getPartitions(), because
+	 * we want more information about partitions than just the parent-child
+	 * relationship.
+	 */
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid, inhparent "
+						 "FROM pg_inherits "
+						 "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -5671,6 +5678,70 @@ getInherits(Archive *fout, int *numInherits)
 }
 
 /*
+ * getPartitions
+ *	  read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	PartInfo    *partinfo;
+
+	int			i_partrelid;
+	int			i_partparent;
+	int			i_partbound;
+
+	/* Before version 10, there are no partitions  */
+	if (fout->remoteVersion < 100000)
+	{
+		*numPartitions = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	/* find the inheritance and boundary information about partitions */
+
+	appendPQExpBufferStr(query,
+						 "SELECT inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -14249,6 +14320,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (tbinfo->reloftype && !dopt->binary_upgrade)
 			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 
+		if (tbinfo->partitionOf && !dopt->binary_upgrade)
+		{
+			TableInfo  *parentRel = tbinfo->partitionOf;
+
+			appendPQExpBuffer(q, " PARTITION OF ");
+			if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+				appendPQExpBuffer(q, "%s.",
+								fmtId(parentRel->dobj.namespace->dobj.name));
+			appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+		}
+
 		if (tbinfo->relkind != RELKIND_MATVIEW)
 		{
 			/* Dump the attributes */
@@ -14277,8 +14359,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
-					/* Skip column if fully defined by reloftype */
-					if (tbinfo->reloftype &&
+					/*
+					 * Skip column if fully defined by reloftype or the
+					 * partition parent.
+					 */
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
 						!has_default && !has_notnull && !dopt->binary_upgrade)
 						continue;
 
@@ -14307,7 +14392,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					}
 
 					/* Attribute type */
-					if (tbinfo->reloftype && !dopt->binary_upgrade)
+					if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade)
 					{
 						appendPQExpBufferStr(q, " WITH OPTIONS");
 					}
@@ -14365,15 +14451,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (actual_atts)
 				appendPQExpBufferStr(q, "\n)");
-			else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+			else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+						!dopt->binary_upgrade))
 			{
 				/*
 				 * We must have a parenthesized attribute list, even though
-				 * empty, when not using the OF TYPE syntax.
+				 * empty, when not using the OF TYPE or PARTITION OF syntax.
 				 */
 				appendPQExpBufferStr(q, " (\n)");
 			}
 
+			if (tbinfo->partitiondef && !dopt->binary_upgrade)
+			{
+				appendPQExpBufferStr(q, "\n");
+				appendPQExpBufferStr(q, tbinfo->partitiondef);
+			}
+
 			if (numParents > 0 && !dopt->binary_upgrade)
 			{
 				appendPQExpBufferStr(q, "\nINHERITS (");
@@ -14543,6 +14636,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f47e535c24..395a9f3288 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -322,6 +322,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index baa5e859d7..f0d955be4f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1808,6 +1808,34 @@ describeOneTableDetails(const char *schemaname,
 	}
 
 	/* Make footers */
+	if (pset.sversion >= 90600)
+	{
+		/* Get the partition information  */
+		PGresult   *result;
+		char	   *parent_name;
+		char	   *partdef;
+
+		printfPQExpBuffer(&buf,
+			 "SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2587,8 +2615,12 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print inherited tables */
-		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+		/* print inherited tables (exclude, if parent is a partitioned table) */
+		printfPQExpBuffer(&buf,
+				"SELECT c.oid::pg_catalog.regclass"
+				" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+				" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+				" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
 
 		result = PSQLexec(buf.data);
 		if (!result)
@@ -2617,9 +2649,23 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-		/* print child tables */
-		if (pset.sversion >= 80300)
-			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+		/* print child tables (with additional info if partitions) */
+		if (pset.sversion >= 100000)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+		else if (pset.sversion >= 80300)
+			printfPQExpBuffer(&buf,
+					"SELECT c.oid::pg_catalog.regclass"
+					" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+					" WHERE c.oid=i.inhrelid AND"
+					" i.inhparent = '%s' AND"
+					" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+					" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
 		else
 			printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
 
@@ -2634,24 +2680,39 @@ describeOneTableDetails(const char *schemaname,
 			/* print the number of child tables, if any */
 			if (tuples > 0)
 			{
-				printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				if (tableinfo.relkind != 'P')
+					printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+				else
+					printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
 				printTableAddFooter(&cont, buf.data);
 			}
 		}
 		else
 		{
 			/* display the list of child tables */
-			const char *ct = _("Child tables");
+			const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
 			int			ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
 
 			for (i = 0; i < tuples; i++)
 			{
-				if (i == 0)
-					printfPQExpBuffer(&buf, "%s: %s",
-									  ct, PQgetvalue(result, i, 0));
+				if (tableinfo.relkind != 'P')
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s",
+										  ct, PQgetvalue(result, i, 0));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s",
+										  ctw, "", PQgetvalue(result, i, 0));
+				}
 				else
-					printfPQExpBuffer(&buf, "%*s  %s",
-									  ctw, "", PQgetvalue(result, i, 0));
+				{
+					if (i == 0)
+						printfPQExpBuffer(&buf, "%s: %s %s",
+										  ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+					else
+						printfPQExpBuffer(&buf, "%*s  %s %s",
+										  ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+				}
 				if (i < tuples - 1)
 					appendPQExpBufferChar(&buf, ',');
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index c3afca687f..b40a18aec2 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -601,6 +601,46 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+               Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+    "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+               Table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+               Table "public.parted"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           |          | 
+ b      | integer |           | not null | 0
+Partition key: LIST (a)
+Check constraints:
+    "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 -- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7818bc01f6..69848e3094 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -565,6 +565,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
 -- partitions cannot be dropped directly
 DROP TABLE part_a;
 
-- 
2.11.0

0005-Teach-a-few-places-to-use-partition-check-quals-20.patchtext/x-diff; name=0005-Teach-a-few-places-to-use-partition-check-quals-20.patchDownload
From 9eed2857e0f16d0611fa37598578b1aaf9199d08 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 5/7] Teach a few places to use partition check quals.

For example, if a row is inserted directly into a partition we should make
sure that it does not violate its bounds.  So teach copy.c and execMain.c
to apply "partition check constraint".

Also, for constraint exclusion to work with partitioned tables, teach the
optimizer to include check constraint expressions derived from partition bound
bound info in the list of predicates it uses to perform the task.
---
 src/backend/commands/copy.c            |   3 +-
 src/backend/executor/execMain.c        |  69 ++++++++-
 src/backend/executor/nodeModifyTable.c |   4 +-
 src/backend/optimizer/util/plancat.c   |  20 +++
 src/include/nodes/execnodes.h          |   4 +
 src/test/regress/expected/inherit.out  | 272 +++++++++++++++++++++++++++++++++
 src/test/regress/expected/insert.out   |  85 +++++++++++
 src/test/regress/expected/update.out   |  27 ++++
 src/test/regress/sql/inherit.sql       |  52 +++++++
 src/test/regress/sql/insert.sql        |  59 +++++++
 src/test/regress/sql/update.sql        |  21 +++
 11 files changed, 610 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b30c2c788e..de9e29c81e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
 			else
 			{
 				/* Check the constraints of the tuple */
-				if (cstate->rel->rd_att->constr)
+				if (cstate->rel->rd_att->constr ||
+					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index badca109a3..21b18b82b9 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
+	resultRelInfo->ri_PartitionCheck =
+						RelationGetPartitionQual(resultRelationDesc, true);
 }
 
 /*
@@ -1693,6 +1696,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	return NULL;
 }
 
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition constraint.
+ *
+ * Note: This is called *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+				   EState *estate)
+{
+	ExprContext *econtext;
+
+	/*
+	 * If first time through, build expression state tree for the partition
+	 * check expression.  Keep it in the per-query memory context so they'll
+	 * survive throughout the query.
+	 */
+	if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+	{
+		List *qual = resultRelInfo->ri_PartitionCheck;
+
+		resultRelInfo->ri_PartitionCheckExpr = (List *)
+									ExecPrepareExpr((Expr *) qual, estate);
+	}
+
+	/*
+	 * We will use the EState's per-tuple context for evaluating constraint
+	 * expressions (creating it if it's not already there).
+	 */
+	econtext = GetPerTupleExprContext(estate);
+
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * As in case of the catalogued constraints, we treat a NULL result as
+	 * success here, not a failure.
+	 */
+	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
@@ -1704,9 +1747,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	Bitmapset  *insertedCols;
 	Bitmapset  *updatedCols;
 
-	Assert(constr);
+	Assert(constr || resultRelInfo->ri_PartitionCheck);
 
-	if (constr->has_not_null)
+	if (constr && constr->has_not_null)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -1737,7 +1780,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		}
 	}
 
-	if (constr->num_check > 0)
+	if (constr && constr->num_check > 0)
 	{
 		const char *failed;
 
@@ -1761,6 +1804,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(rel, failed)));
 		}
 	}
+
+	if (resultRelInfo->ri_PartitionCheck &&
+		!ExecPartitionCheck(resultRelInfo, slot, estate))
+	{
+		char	   *val_desc;
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupdesc,
+												 modifiedCols,
+												 64);
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("new row for relation \"%s\" violates partition constraint",
+						RelationGetRelationName(rel)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
 }
 
 /*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29d5f5786a..6eccfb7cec 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,7 @@ ExecInsert(ModifyTableState *mtstate,
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -922,7 +922,7 @@ lreplace:;
 		/*
 		 * Check the constraints of the tuple
 		 */
-		if (resultRelationDesc->rd_att->constr)
+		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
 			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index bb16c59028..72272d9bb7 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
@@ -1140,6 +1141,7 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+	List		*pcqual;
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1225,6 +1227,24 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+	/* Append partition predicates, if any */
+	pcqual = RelationGetPartitionQual(relation, true);
+	if (pcqual)
+	{
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization similar to check constraints.
+		 */
+		pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
+		pcqual = (List *) canonicalize_qual((Expr *) pcqual);
+
+		/* Fix Vars to have the desired varno */
+		if (varno != 1)
+			ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+		result = list_concat(result, pcqual);
+	}
+
 	heap_close(relation, NoLock);
 
 	return result;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8004d856cc..df2dec3a2c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,8 @@ typedef struct JunkFilter
  *		projectReturning		for computing a RETURNING list
  *		onConflictSetProj		for computing ON CONFLICT DO UPDATE SET
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
+ *		PartitionCheck			partition check expression
+ *		PartitionCheckExpr		partition check expression state
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -344,6 +346,8 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
 	List	   *ri_onConflictSetWhere;
+	List	   *ri_PartitionCheck;
+	List	   *ri_PartitionCheckExpr;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index b331828e5d..38ea8e86f3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,275 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+   ->  Seq Scan on part_ab_cd
+   ->  Seq Scan on part_ef_gh
+   ->  Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+           QUERY PLAN           
+--------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+           QUERY PLAN            
+---------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ab_cd
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_ef_gh
+         Filter: (a IS NOT NULL)
+   ->  Seq Scan on part_null_xy
+         Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+   ->  Seq Scan on part_ef_gh
+         Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ab_cd
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_ef_gh
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+   ->  Seq Scan on part_null_xy
+         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+                QUERY PLAN                
+------------------------------------------
+ Append
+   ->  Seq Scan on list_parted
+         Filter: ((a)::text = 'ab'::text)
+   ->  Seq Scan on part_ab_cd
+         Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+   ->  Seq Scan on part_1_10
+   ->  Seq Scan on part_10_20
+   ->  Seq Scan on part_21_30
+   ->  Seq Scan on part_40_inf
+   ->  Seq Scan on part_1_10_ab
+   ->  Seq Scan on part_1_10_cd
+   ->  Seq Scan on part_10_20_ab
+   ->  Seq Scan on part_10_20_cd
+   ->  Seq Scan on part_21_30_ab
+   ->  Seq Scan on part_21_30_cd
+   ->  Seq Scan on part_40_inf_ab
+   ->  Seq Scan on part_40_inf_cd
+   ->  Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (a = 5)
+   ->  Seq Scan on part_1_10_cd
+         Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_1_10_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_10_20_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_21_30_ab
+         Filter: (b = 'ab'::bpchar)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_1_10
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_10_20
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_21_30
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf
+         Filter: (b IS NULL)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+                   QUERY PLAN                   
+------------------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_1_10_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_10_20_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_21_30_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_ab
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_cd
+         Filter: ((a IS NOT NULL) AND (a < 67))
+   ->  Seq Scan on part_40_inf_null
+         Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Seq Scan on range_list_parted
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_ab
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_cd
+         Filter: (a >= 30)
+   ->  Seq Scan on part_40_inf_null
+         Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE:  drop cascades to 13 other objects
+DETAIL:  drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30
+drop cascades to table part_21_30_ab
+drop cascades to table part_21_30_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d71c3..2e78fd9fc1 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,88 @@ Rules:
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, (b+0));
+create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part1 values ('a', 11);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (a, 11).
+insert into part1 values ('b', 1);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+-- ok
+insert into part1 values ('a', 1);
+-- fail
+insert into part4 values ('b', 21);
+ERROR:  new row for relation "part4" violates partition constraint
+DETAIL:  Failing row contains (b, 21).
+insert into part4 values ('a', 10);
+ERROR:  new row for relation "part4" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+insert into part4 values ('b', 10);
+-- fail (partition key a has a NOT NULL constraint)
+insert into part1 values (null);
+ERROR:  null value in column "a" violates not-null constraint
+DETAIL:  Failing row contains (null, null).
+-- fail (expression key (b+0) cannot be null either)
+insert into part1 values (1);
+ERROR:  new row for relation "part1" violates partition constraint
+DETAIL:  Failing row contains (1, null).
+create table list_parted (
+	a text,
+	b int
+) partition by list (lower(a));
+create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
+create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_aa_bb values ('cc', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+insert into part_aa_bb values ('AAa', 1);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (AAa, 1).
+insert into part_aa_bb values (null);
+ERROR:  new row for relation "part_aa_bb" violates partition constraint
+DETAIL:  Failing row contains (null, null).
+-- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- fail
+insert into part_ee_ff1 values ('EE', 11);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (EE, 11).
+-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
+insert into part_ee_ff1 values ('cc', 1);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (cc, 1).
+-- ok
+insert into part_ee_ff1 values ('ff', 1);
+insert into part_ee_ff2 values ('ff', 11);
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part4
+drop table list_parted cascade;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to table part_ee_ff
+drop cascades to table part_ee_ff1
+drop cascades to table part_ee_ff2
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 609899e1f7..a1e9255450 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -198,3 +198,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+ERROR:  new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (b, 9).
+-- ok
+update range_parted set b = b + 1 where b = 10;
+-- cleanup
+drop table range_parted cascade;
+NOTICE:  drop cascades to 4 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
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1ac6..e22a14ebda 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,55 @@ FROM generate_series(1, 3) g(i);
 reset enable_seqscan;
 reset enable_indexscan;
 reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+	a	varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+	a	int,
+	b	char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_ab partition of part_21_30 for values in ('ab');
+create table part_21_30_cd partition of part_21_30 for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+
+/* Should select no rows because range partition key cannot be null */
+explain (costs off) select * from range_list_parted where a is null;
+
+/* Should only select rows from the null-accepting partition */
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d46d..eb923646c3 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ create rule irule3 as on insert to inserttest2 do also
 drop table inserttest2;
 drop table inserttest;
 drop type insert_test_type;
+
+-- direct partition inserts should check partition bound constraint
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, (b+0));
+create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
+
+-- fail
+insert into part1 values ('a', 11);
+insert into part1 values ('b', 1);
+-- ok
+insert into part1 values ('a', 1);
+-- fail
+insert into part4 values ('b', 21);
+insert into part4 values ('a', 10);
+-- ok
+insert into part4 values ('b', 10);
+
+-- fail (partition key a has a NOT NULL constraint)
+insert into part1 values (null);
+-- fail (expression key (b+0) cannot be null either)
+insert into part1 values (1);
+
+create table list_parted (
+	a text,
+	b int
+) partition by list (lower(a));
+create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
+create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- fail
+insert into part_aa_bb values ('cc', 1);
+insert into part_aa_bb values ('AAa', 1);
+insert into part_aa_bb values (null);
+-- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
+
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+
+-- fail
+insert into part_ee_ff1 values ('EE', 11);
+-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
+insert into part_ee_ff1 values ('cc', 1);
+-- ok
+insert into part_ee_ff1 values ('ff', 1);
+insert into part_ee_ff2 values ('ff', 11);
+
+-- cleanup
+drop table range_parted cascade;
+drop table list_parted cascade;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index ad58273b38..d7721ed376 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -106,3 +106,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
 
 DROP TABLE update_test;
 DROP TABLE upsert_test;
+
+-- update to a partition should check partition bound constraint for the new tuple
+create table range_parted (
+	a text,
+	b int
+) partition by range (a, b);
+create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+insert into part_a_1_a_10 values ('a', 1);
+insert into part_b_10_b_20 values ('b', 10);
+
+-- fail
+update part_a_1_a_10 set a = 'b' where a = 'a';
+update range_parted set b = b - 1 where b = 10;
+-- ok
+update range_parted set b = b + 1 where b = 10;
+
+-- cleanup
+drop table range_parted cascade;
-- 
2.11.0

0006-Tuple-routing-for-partitioned-tables-20.patchtext/x-diff; name=0006-Tuple-routing-for-partitioned-tables-20.patchDownload
From 4a2936dcd6a1ce0032e771037a0af88e930672a4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 6/7] Tuple routing for partitioned tables.

Both COPY FROM and INSERT are covered by this commit.  Routing to foreing
partitions is not supported at the moment.

To implement tuple-routing, introduce a PartitionDispatch data structure.
Each partitioned table in a partition tree gets one and contains info
such as a pointer to its partition descriptor, partition key execution
state, global sequence numbers of its leaf partitions and a way to link
to the PartitionDispatch objects of any of its partitions that are
partitioned themselves. Starting with the PartitionDispatch object of the
root partitioned table and a tuple to route, one can get the global
sequence number of the leaf partition that the tuple gets routed to,
if one exists.
---
 src/backend/catalog/partition.c        | 328 ++++++++++++++++++++++++++++++++-
 src/backend/commands/copy.c            | 171 ++++++++++++++++-
 src/backend/commands/tablecmds.c       |   1 +
 src/backend/executor/execMain.c        |  58 +++++-
 src/backend/executor/nodeModifyTable.c | 147 +++++++++++++++
 src/backend/parser/analyze.c           |   8 +
 src/include/catalog/partition.h        |  35 ++++
 src/include/executor/executor.h        |   6 +
 src/include/nodes/execnodes.h          |  10 +
 src/test/regress/expected/insert.out   |  55 ++++++
 src/test/regress/sql/insert.sql        |  27 +++
 11 files changed, 840 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0a4f95fc3f..b2cb742264 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -129,6 +129,9 @@ static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 static int32 partition_rbound_cmp(PartitionKey key,
 					 Datum *datums1, RangeDatumContent *content1, bool lower1,
 					 PartitionRangeBound *b2);
+static int32 partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums);
 
 static int32 partition_bound_cmp(PartitionKey key,
 					PartitionBoundInfo boundinfo,
@@ -137,6 +140,13 @@ static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
 
+/* Support get_partition_for_tuple() */
+static void FormPartitionKeyDatum(PartitionDispatch pd,
+							TupleTableSlot *slot,
+							EState *estate,
+							Datum *values,
+							bool *isnull);
+
 /*
  * RelationBuildPartitionDesc
  *		Form rel's partition descriptor
@@ -916,6 +926,119 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
+/* Turn an array of OIDs with N elements into a list */
+#define OID_ARRAY_TO_LIST(arr, N, list) \
+	do\
+	{\
+		int		i;\
+		for (i = 0; i < (N); i++)\
+			(list) = lappend_oid((list), (arr)[i]);\
+	} while(0)
+
+/*
+ * RelationGetPartitionDispatchInfo
+ *		Returns information necessary to route tuples down a partition tree
+ *
+ * All the partitions will be locked with lockmode, unless it is NoLock.
+ * A list of the OIDs of all the leaf partition of rel is returned in
+ * *leaf_part_oids.
+ */
+PartitionDispatch *
+RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
+								 int *num_parted, List **leaf_part_oids)
+{
+	PartitionDesc	rootpartdesc = RelationGetPartitionDesc(rel);
+	PartitionDispatchData **pd;
+	List	   *all_parts = NIL,
+			   *parted_rels;
+	ListCell   *lc;
+	int			i,
+				k;
+
+	/*
+	 * Lock partitions and make a list of the partitioned ones to prepare
+	 * their PartitionDispatch objects below.
+	 *
+	 * Cannot use find_all_inheritors() here, because then the order of OIDs
+	 * in parted_rels list would be unknown, which does not help, because we
+	 * we assign indexes within individual PartitionDispatch in an order that
+	 * is predetermined (determined by the order of OIDs in individual
+	 * partition descriptors).
+	 */
+	*num_parted = 1;
+	parted_rels = list_make1(rel);
+	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
+	foreach(lc, all_parts)
+	{
+		Relation		partrel = heap_open(lfirst_oid(lc), lockmode);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+
+		/*
+		 * If this partition is a partitioned table, add its children to the
+		 * end of the list, so that they are processed as well.
+		 */
+		if (partdesc)
+		{
+			(*num_parted)++;
+			parted_rels = lappend(parted_rels, partrel);
+			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+		}
+		else
+			heap_close(partrel, NoLock);
+
+		/*
+		 * We keep the partitioned ones open until we're done using the
+		 * information being collected here (for example, see
+		 * ExecEndModifyTable).
+		 */
+	}
+
+	/* Generate PartitionDispatch objects for all partitioned tables */
+	pd = (PartitionDispatchData **) palloc(*num_parted *
+										sizeof(PartitionDispatchData *));
+	*leaf_part_oids = NIL;
+	i = k = 0;
+	foreach(lc, parted_rels)
+	{
+		Relation		partrel = lfirst(lc);
+		PartitionKey	partkey = RelationGetPartitionKey(partrel);
+		PartitionDesc	partdesc = RelationGetPartitionDesc(partrel);
+		int			j,
+					m;
+
+		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+		pd[i]->reldesc = partrel;
+		pd[i]->key = partkey;
+		pd[i]->keystate = NIL;
+		pd[i]->partdesc = partdesc;
+		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
+
+		m = 0;
+		for (j = 0; j < partdesc->nparts; j++)
+		{
+			Oid		partrelid = partdesc->oids[j];
+
+			if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
+			{
+				*leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
+				pd[i]->indexes[j] = k++;
+			}
+			else
+			{
+				/*
+				 * We can assign indexes this way because of the way
+				 * parted_rels has been generated.
+				 */
+				pd[i]->indexes[j] = -(i + 1 + m);
+				m++;
+			}
+		}
+		i++;
+	}
+
+	return pd;
+}
+
 /* Module-local functions */
 
 /*
@@ -1367,6 +1490,176 @@ generate_partition_qual(Relation rel, bool recurse)
 	return result;
 }
 
+/* ----------------
+ *		FormPartitionKeyDatum
+ *			Construct values[] and isnull[] arrays for the partition key
+ *			of a tuple.
+ *
+ *	pkinfo			partition key execution info
+ *	slot			Heap tuple from which to extract partition key
+ *	estate			executor state for evaluating any partition key
+ *					expressions (must be non-NULL)
+ *	values			Array of partition key Datums (output area)
+ *	isnull			Array of is-null indicators (output area)
+ *
+ * the ecxt_scantuple slot of estate's per-tuple expr context must point to
+ * the heap tuple passed in.
+ * ----------------
+ */
+static void
+FormPartitionKeyDatum(PartitionDispatch pd,
+					  TupleTableSlot *slot,
+					  EState *estate,
+					  Datum *values,
+					  bool *isnull)
+{
+	ListCell   *partexpr_item;
+	int			i;
+
+	if (pd->key->partexprs != NIL && pd->keystate == NIL)
+	{
+		/* Check caller has set up context correctly */
+		Assert(estate != NULL &&
+			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+
+		/* First time through, set up expression evaluation state */
+		pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
+												estate);
+	}
+
+	partexpr_item = list_head(pd->keystate);
+	for (i = 0; i < pd->key->partnatts; i++)
+	{
+		AttrNumber	keycol = pd->key->partattrs[i];
+		Datum		datum;
+		bool		isNull;
+
+		if (keycol != 0)
+		{
+			/* Plain column; get the value directly from the heap tuple */
+			datum = slot_getattr(slot, keycol, &isNull);
+		}
+		else
+		{
+			/* Expression; need to evaluate it */
+			if (partexpr_item == NULL)
+				elog(ERROR, "wrong number of partition key expressions");
+			datum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+											   GetPerTupleExprContext(estate),
+											   &isNull,
+											   NULL);
+			partexpr_item = lnext(partexpr_item);
+		}
+		values[i] = datum;
+		isnull[i] = isNull;
+	}
+
+	if (partexpr_item != NULL)
+		elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ *		Finds a leaf partition for tuple contained in *slot
+ *
+ * Returned value is the sequence number of the leaf partition thus found,
+ * or -1 if no leaf partition is found for the tuple.  *failed_at is set
+ * to the OID of the partitioned table whose partition was not found in
+ * the latter case.
+ */
+int
+get_partition_for_tuple(PartitionDispatch *pd,
+						TupleTableSlot *slot,
+						EState *estate,
+						Oid *failed_at)
+{
+	PartitionDispatch parent;
+	Datum	values[PARTITION_MAX_KEYS];
+	bool	isnull[PARTITION_MAX_KEYS];
+	int		cur_offset,
+			cur_index;
+	int		i;
+
+	/* start with the root partitioned table */
+	parent = pd[0];
+	while(true)
+	{
+		PartitionKey	key = parent->key;
+		PartitionDesc	partdesc = parent->partdesc;
+
+		/* Quick exit */
+		if (partdesc->nparts == 0)
+		{
+			*failed_at = RelationGetRelid(parent->reldesc);
+			return -1;
+		}
+
+		/* Extract partition key from tuple */
+		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
+
+		if (key->strategy == PARTITION_STRATEGY_RANGE)
+		{
+			/* Disallow nulls in the range partition key of the tuple */
+			for (i = 0; i < key->partnatts; i++)
+				if (isnull[i])
+					ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("range partition key of row contains null")));
+		}
+
+		if (partdesc->boundinfo->has_null && isnull[0])
+			/* Tuple maps to the null-accepting list partition */
+			cur_index = partdesc->boundinfo->null_index;
+		else
+		{
+			/* Else bsearch in partdesc->boundinfo */
+			bool	equal = false;
+
+			cur_offset = partition_bound_bsearch(key, partdesc->boundinfo,
+												 values, false, &equal);
+			switch (key->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+					if (cur_offset >= 0 && equal)
+						cur_index = partdesc->boundinfo->indexes[cur_offset];
+					else
+						cur_index = -1;
+					break;
+
+				case PARTITION_STRATEGY_RANGE:
+					/*
+					 * Offset returned is such that the bound at offset is
+					 * found to be less or equal with the tuple. So, the
+					 * bound at offset+1 would be the upper bound.
+					 */
+					cur_index = partdesc->boundinfo->indexes[cur_offset+1];
+					break;
+
+				default:
+					elog(ERROR, "unexpected partition strategy: %d",
+								(int) key->strategy);
+			}
+		}
+
+		/*
+		 * cur_index < 0 means we failed to find a partition of this parent.
+		 * cur_index >= 0 means we either found the leaf partition, or the
+		 * next parent to find a partition of.
+		 */
+		if (cur_index < 0)
+		{
+			*failed_at = RelationGetRelid(parent->reldesc);
+			return -1;
+		}
+		else if (parent->indexes[cur_index] < 0)
+			parent = pd[-parent->indexes[cur_index]];
+		else
+			break;
+	}
+
+	return parent->indexes[cur_index];
+}
+
 /*
  * qsort_partition_list_value_cmp
  *
@@ -1499,6 +1792,36 @@ partition_rbound_cmp(PartitionKey key,
 }
 
 /*
+ * partition_rbound_datum_cmp
+ *
+ * Return whether range bound (specified in rb_datums, rb_content, and
+ * rb_lower) <=, =, >= partition key of tuple (tuple_datums)
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key,
+						   Datum *rb_datums, RangeDatumContent *rb_content,
+						   Datum *tuple_datums)
+{
+	int		i;
+	int32	cmpval = -1;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (rb_content[i] != RANGE_DATUM_FINITE)
+			return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 rb_datums[i],
+												 tuple_datums[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	return cmpval;
+}
+
+/*
  * partition_bound_cmp
  * 
  * Return whether the bound at offset in boundinfo is <=, =, >= the argument
@@ -1537,7 +1860,10 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 											  bound_datums, content, lower,
 											  (PartitionRangeBound *) probe);
 			}
-
+			else
+				cmpval = partition_rbound_datum_cmp(key,
+													bound_datums, content,
+													(Datum *) probe);
 			break;
 		}
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index de9e29c81e..3f8d748a82 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,11 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+	PartitionDispatch	   *partition_dispatch_info;
+	int						num_dispatch;
+	int						num_partitions;
+	ResultRelInfo		   *partitions;
+	TupleConversionMap	  **partition_tupconv_maps;
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1397,6 +1402,71 @@ BeginCopy(ParseState *pstate,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
+
+		/*
+		 * Initialize state for CopyFrom tuple routing.  Watch out for
+		 * any foreign partitions.
+		 */
+		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			PartitionDispatch *pd;
+			List		   *leaf_parts;
+			ListCell	   *cell;
+			int				i,
+							num_parted,
+							num_leaf_parts;
+			ResultRelInfo  *leaf_part_rri;
+
+			/* Get the tuple-routing information and lock partitions */
+			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+												  &num_parted, &leaf_parts);
+			num_leaf_parts = list_length(leaf_parts);
+			cstate->partition_dispatch_info = pd;
+			cstate->num_dispatch = num_parted;
+			cstate->num_partitions = num_leaf_parts;
+			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
+														sizeof(ResultRelInfo));
+			cstate->partition_tupconv_maps = (TupleConversionMap **)
+						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+			leaf_part_rri = cstate->partitions;
+			i = 0;
+			foreach(cell, leaf_parts)
+			{
+				Relation	partrel;
+
+				/*
+				 * We locked all the partitions above including the leaf
+				 * partitions.  Note that each of the relations in
+				 * cstate->partitions will be closed by CopyFrom() after
+				 * it's finished with its processing.
+				 */
+				partrel = heap_open(lfirst_oid(cell), NoLock);
+
+				/*
+				 * Verify result relation is a valid target for the current
+				 * operation.
+				 */
+				CheckValidResultRel(partrel, CMD_INSERT);
+
+				InitResultRelInfo(leaf_part_rri,
+								  partrel,
+								  1,	 /* dummy */
+								  false, /* no partition constraint check */
+								  0);
+
+				/* Open partition indices */
+				ExecOpenIndices(leaf_part_rri, false);
+
+				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
+					cstate->partition_tupconv_maps[i] =
+								convert_tuples_by_name(tupDesc,
+									RelationGetDescr(partrel),
+									gettext_noop("could not convert row type"));
+				leaf_part_rri++;
+				i++;
+			}
+		}
 	}
 	else
 	{
@@ -2255,6 +2325,7 @@ CopyFrom(CopyState cstate)
 	Datum	   *values;
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2281,6 +2352,7 @@ CopyFrom(CopyState cstate)
 	 * only hint about them in the view case.)
 	 */
 	if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+		cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
 		!(cstate->rel->trigdesc &&
 		  cstate->rel->trigdesc->trig_insert_instead_row))
 	{
@@ -2391,6 +2463,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
+					  true,		/* do load partition check expression */
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2491,7 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+		cstate->partition_dispatch_info != NULL ||
 		cstate->volatile_defexprs)
 	{
 		useHeapMultiInsert = false;
@@ -2442,7 +2516,11 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
+	else
+		bistate = NULL;
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2494,6 +2572,59 @@ CopyFrom(CopyState cstate)
 		slot = myslot;
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
+		/* Determine the partition to heap_insert the tuple into */
+		if (cstate->partition_dispatch_info)
+		{
+			int		leaf_part_index;
+			TupleConversionMap *map;
+
+			/*
+			 * Away we go ... If we end up not finding a partition after all,
+			 * ExecFindPartition() does not return and errors out instead.
+			 * Otherwise, the returned value is to be used as an index into
+			 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+			 * will get us the ResultRelInfo and TupleConversionMap for the
+			 * partition, respectively.
+			 */
+			leaf_part_index = ExecFindPartition(resultRelInfo,
+											cstate->partition_dispatch_info,
+												slot,
+												estate);
+			Assert(leaf_part_index >= 0 &&
+				   leaf_part_index < cstate->num_partitions);
+
+			/*
+			 * Save the old ResultRelInfo and switch to the one corresponding
+			 * to the selected partition.
+			 */
+			saved_resultRelInfo = resultRelInfo;
+			resultRelInfo = cstate->partitions + leaf_part_index;
+
+			/* We do not yet have a way to insert into a foreign partition */
+			if (resultRelInfo->ri_FdwRoutine)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot route inserted tuples to a foreign table")));
+
+			/*
+			 * For ExecInsertIndexTuples() to work on the partition's indexes
+			 */
+			estate->es_result_relation_info = resultRelInfo;
+
+			/*
+			 * We might need to convert from the parent rowtype to the
+			 * partition rowtype.
+			 */
+			map = cstate->partition_tupconv_maps[leaf_part_index];
+			if (map)
+			{
+				tuple = do_convert_tuple(tuple, map);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+			}
+
+			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		}
+
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
@@ -2553,7 +2684,8 @@ CopyFrom(CopyState cstate)
 					List	   *recheckIndexes = NIL;
 
 					/* OK, store the tuple and create index entries for it */
-					heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
+								hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2577,6 +2709,12 @@ CopyFrom(CopyState cstate)
 			 * tuples inserted by an INSERT command.
 			 */
 			processed++;
+
+			if (saved_resultRelInfo)
+			{
+				resultRelInfo = saved_resultRelInfo;
+				estate->es_result_relation_info = resultRelInfo;
+			}
 		}
 	}
 
@@ -2590,7 +2728,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2614,6 +2753,32 @@ CopyFrom(CopyState cstate)
 
 	ExecCloseIndices(resultRelInfo);
 
+	/* Close all the partitioned tables, leaf partitions, and their indices */
+	if (cstate->partition_dispatch_info)
+	{
+		int		i;
+
+		/*
+		 * Remember cstate->partition_dispatch_info[0] corresponds to the root
+		 * partitioned table, which we must not try to close, because it is
+		 * the main target table of COPY that will be closed eventually by
+		 * DoCopy().
+		 */
+		for (i = 1; i < cstate->num_dispatch; i++)
+		{
+			PartitionDispatch pd = cstate->partition_dispatch_info[i];
+
+			heap_close(pd->reldesc, NoLock);
+		}
+		for (i = 0; i < cstate->num_partitions; i++)
+		{
+			ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+			ExecCloseIndices(resultRelInfo);
+			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		}
+	}
+
 	FreeExecutorState(estate);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8a803233ca..c77b216d4f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1322,6 +1322,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
+						  false,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 21b18b82b9..0f47c7e010 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
+							  true,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1215,6 +1216,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	resultRelInfo->ri_PartitionCheck =
-						RelationGetPartitionQual(resultRelationDesc, true);
+	if (load_partition_check)
+		resultRelInfo->ri_PartitionCheck =
+							RelationGetPartitionQual(resultRelationDesc,
+													 true);
 }
 
 /*
@@ -1316,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
+					  true,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -2991,3 +2996,52 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
+
+/*
+ * ExecFindPartition -- Find a leaf partition in the partition tree rooted
+ * at parent, for the heap tuple contained in *slot
+ *
+ * estate must be non-NULL; we'll need it to compute any expressions in the
+ * partition key(s)
+ *
+ * If no leaf partition is found, this routine errors out with the appropriate
+ * error message, else it returns the leaf partition sequence number returned
+ * by get_partition_for_tuple() unchanged.
+ */
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+				  TupleTableSlot *slot, EState *estate)
+{
+	int		result;
+	Oid		failed_at;
+	ExprContext *econtext = GetPerTupleExprContext(estate);
+
+	econtext->ecxt_scantuple = slot;
+	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
+	if (result < 0)
+	{
+		Relation	rel = resultRelInfo->ri_RelationDesc;
+		char	   *val_desc;
+		Bitmapset  *insertedCols,
+				   *updatedCols,
+				   *modifiedCols;
+		TupleDesc	tupDesc = RelationGetDescr(rel);
+
+		insertedCols = GetInsertedColumns(resultRelInfo, estate);
+		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+		modifiedCols = bms_union(insertedCols, updatedCols);
+		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+												 slot,
+												 tupDesc,
+												 modifiedCols,
+												 64);
+		Assert(OidIsValid(failed_at));
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+				 errmsg("no partition of relation \"%s\" found for row",
+						get_rel_name(failed_at)),
+		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+	}
+
+	return result;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6eccfb7cec..c0b58d1841 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
 {
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+
+	/* Determine the partition to heap_insert the tuple into */
+	if (mtstate->mt_partition_dispatch_info)
+	{
+		int		leaf_part_index;
+		TupleConversionMap *map;
+
+		/*
+		 * Away we go ... If we end up not finding a partition after all,
+		 * ExecFindPartition() does not return and errors out instead.
+		 * Otherwise, the returned value is to be used as an index into
+		 * arrays mt_partitions[] and mt_partition_tupconv_maps[] that
+		 * will get us the ResultRelInfo and TupleConversionMap for the
+		 * partition, respectively.
+		 */
+		leaf_part_index = ExecFindPartition(resultRelInfo,
+										mtstate->mt_partition_dispatch_info,
+											slot,
+											estate);
+		Assert(leaf_part_index >= 0 &&
+			   leaf_part_index < mtstate->mt_num_partitions);
+
+		/*
+		 * Save the old ResultRelInfo and switch to the one corresponding to
+		 * the selected partition.
+		 */
+		saved_resultRelInfo = resultRelInfo;
+		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
+
+		/* We do not yet have a way to insert into a foreign partition */
+		if (resultRelInfo->ri_FdwRoutine)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot route inserted tuples to a foreign table")));
+
+		/* For ExecInsertIndexTuples() to work on the partition's indexes */
+		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * We might need to convert from the parent rowtype to the partition
+		 * rowtype.
+		 */
+		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+		if (map)
+		{
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+		}
+	}
+
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/*
@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	Plan	   *subplan;
 	ListCell   *l;
 	int			i;
+	Relation	rel;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1655,6 +1713,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+	/* Build state for INSERT tuple routing */
+	rel = mtstate->resultRelInfo->ri_RelationDesc;
+	if (operation == CMD_INSERT &&
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDispatch  *pd;
+		int					i,
+							j,
+							num_parted,
+							num_leaf_parts;
+		List			   *leaf_parts;
+		ListCell		   *cell;
+		ResultRelInfo	   *leaf_part_rri;
+
+		/* Form the partition node tree and lock partitions */
+		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+											  &num_parted, &leaf_parts);
+		mtstate->mt_partition_dispatch_info = pd;
+		mtstate->mt_num_dispatch = num_parted;
+		num_leaf_parts = list_length(leaf_parts);
+		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_partitions = (ResultRelInfo *)
+						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+		leaf_part_rri = mtstate->mt_partitions;
+		i = j = 0;
+		foreach(cell, leaf_parts)
+		{
+			Oid			partrelid = lfirst_oid(cell);
+			Relation	partrel;
+
+			/*
+			 * We locked all the partitions above including the leaf
+			 * partitions.  Note that each of the relations in
+			 * mtstate->mt_partitions will be closed by ExecEndModifyTable().
+			 */
+			partrel = heap_open(partrelid, NoLock);
+
+			/*
+			 * Verify result relation is a valid target for the current
+			 * operation
+			 */
+			CheckValidResultRel(partrel, CMD_INSERT);
+
+			InitResultRelInfo(leaf_part_rri,
+							  partrel,
+							  1,		/* dummy */
+							  false,	/* no partition constraint checks */
+							  eflags);
+
+			/* Open partition indices (note: ON CONFLICT unsupported)*/
+			if (partrel->rd_rel->relhasindex && operation != CMD_DELETE &&
+				leaf_part_rri->ri_IndexRelationDescs == NULL)
+				ExecOpenIndices(leaf_part_rri, false);
+
+			if (!equalTupleDescs(RelationGetDescr(rel),
+								 RelationGetDescr(partrel)))
+				mtstate->mt_partition_tupconv_maps[i] =
+							convert_tuples_by_name(RelationGetDescr(rel),
+												   RelationGetDescr(partrel),
+								  gettext_noop("could not convert row type"));
+
+			leaf_part_rri++;
+			i++;
+		}
+	}
+
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
@@ -1972,6 +2099,26 @@ ExecEndModifyTable(ModifyTableState *node)
 														   resultRelInfo);
 	}
 
+	/* Close all the partitioned tables, leaf partitions, and their indices
+	 *
+	 * Remember node->mt_partition_dispatch_info[0] corresponds to the root
+	 * partitioned table, which we must not try to close, because it is the
+	 * main target table of the query that will be closed by ExecEndPlan().
+	 */
+	for (i = 1; i < node->mt_num_dispatch; i++)
+	{
+		PartitionDispatch pd = node->mt_partition_dispatch_info[i];
+
+		heap_close(pd->reldesc, NoLock);
+	}
+	for (i = 0; i < node->mt_num_partitions; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+		ExecCloseIndices(resultRelInfo);
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 1a541788eb..7364346167 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/* Process ON CONFLICT, if any. */
 	if (stmt->onConflictClause)
+	{
+		/* Bail out if target relation is partitioned table */
+		if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
 		qry->onConflict = transformOnConflictClause(pstate,
 													stmt->onConflictClause);
+	}
 
 	/*
 	 * If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 70d8325137..21effbf87b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
 #include "parser/parse_node.h"
 #include "utils/rel.h"
 
@@ -37,6 +39,30 @@ typedef struct PartitionDescData
 
 typedef struct PartitionDescData *PartitionDesc;
 
+/*-----------------------
+ * PartitionDispatch - information about one partitioned table in a partition
+ * hiearchy required to route a tuple to one of its partitions
+ *
+ *	reldesc		Relation descriptor of the table
+ *	key			Partition key information of the table
+ *	keystate	Execution state required for expressions in the partition key
+ *	partdesc	Partition descriptor of the table
+ *	indexes		Array with partdesc->nparts members (for details on what
+ *				individual members represent, see how they are set in
+ *				RelationGetPartitionDispatchInfo())
+ *-----------------------
+ */
+typedef struct PartitionDispatchData
+{
+	Relation				reldesc;
+	PartitionKey			key;
+	List				   *keystate;	/* list of ExprState */
+	PartitionDesc			partdesc;
+	int					   *indexes;
+} PartitionDispatchData;
+
+typedef struct PartitionDispatchData *PartitionDispatch;
+
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
@@ -45,4 +71,13 @@ extern void check_new_partition_bound(char *relname, Relation parent, Node *boun
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
 extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
+								 int lockmode, int *num_parted,
+								 List **leaf_part_oids);
+extern int get_partition_for_tuple(PartitionDispatch *pd,
+					TupleTableSlot *slot,
+					EState *estate,
+					Oid *failed_at);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 136276be53..b4d09f9564 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
 #ifndef EXECUTOR_H
 #define EXECUTOR_H
 
+#include "catalog/partition.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
+				  bool load_partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+				  PartitionDispatch *pd,
+				  TupleTableSlot *slot,
+				  EState *estate);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index df2dec3a2c..1de5c8196d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
 #include "nodes/params.h"
@@ -1147,6 +1148,15 @@ typedef struct ModifyTableState
 										 * tlist  */
 	TupleTableSlot *mt_conflproj;		/* CONFLICT ... SET ... projection
 										 * target */
+	struct PartitionDispatchData **mt_partition_dispatch_info;
+										/* Tuple-routing support info */
+	int				mt_num_dispatch;	/* Number of entries in the above
+										 * array */
+	int				mt_num_partitions;	/* Number of members in the
+										 * following arrays */
+	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
+	TupleConversionMap **mt_partition_tupconv_maps;
+									/* Per partition tuple conversion map */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 2e78fd9fc1..561cefa3c4 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -230,6 +230,61 @@ DETAIL:  Failing row contains (cc, 1).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+-- Check tuple routing for partitioned tables
+-- fail
+insert into range_parted values ('a', 0);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 0).
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+ERROR:  no partition of relation "range_parted" found for row
+DETAIL:  Failing row contains (a, 20).
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+-- fail (partition key (b+0) is null)
+insert into range_parted values ('a');
+ERROR:  range partition key of row contains null
+select tableoid::regclass, * from range_parted;
+ tableoid | a | b  
+----------+---+----
+ part1    | a |  1
+ part1    | a |  1
+ part2    | a | 10
+ part3    | b |  1
+ part4    | b | 10
+ part4    | b | 10
+(6 rows)
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_ee_ff not found in both cases)
+insert into list_parted values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+insert into part_ee_ff values ('EE', 0);
+ERROR:  no partition of relation "part_ee_ff" found for row
+DETAIL:  Failing row contains (EE, 0).
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_ee_ff values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+  tableoid   | a  | b  
+-------------+----+----
+ part_aa_bb  | aA |   
+ part_cc_dd  | cC |  1
+ part_null   |    |  0
+ part_null   |    |  1
+ part_ee_ff1 | ff |  1
+ part_ee_ff1 | EE |  1
+ part_ee_ff2 | ff | 11
+ part_ee_ff2 | EE | 10
+(8 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index eb923646c3..846bb5897a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,33 @@ insert into part_ee_ff1 values ('cc', 1);
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
 
+-- Check tuple routing for partitioned tables
+
+-- fail
+insert into range_parted values ('a', 0);
+-- ok
+insert into range_parted values ('a', 1);
+insert into range_parted values ('a', 10);
+-- fail
+insert into range_parted values ('a', 20);
+-- ok
+insert into range_parted values ('b', 1);
+insert into range_parted values ('b', 10);
+-- fail (partition key (b+0) is null)
+insert into range_parted values ('a');
+select tableoid::regclass, * from range_parted;
+
+-- ok
+insert into list_parted values (null, 1);
+insert into list_parted (a) values ('aA');
+-- fail (partition of part_ee_ff not found in both cases)
+insert into list_parted values ('EE', 0);
+insert into part_ee_ff values ('EE', 0);
+-- ok
+insert into list_parted values ('EE', 1);
+insert into part_ee_ff values ('EE', 10);
+select tableoid::regclass, * from list_parted;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patchtext/x-diff; name=0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patchDownload
From 3d2b14cec275bf1f532847807eae63ad7f3d2ea4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 7/7] Update DDL Partitioning chapter to reflect new
 developments.

---
 doc/src/sgml/ddl.sgml | 402 +++++++++++---------------------------------------
 1 file changed, 83 insertions(+), 319 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 7e1bc0e534..9e8e48420b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2823,7 +2823,7 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <para>
       Bulk loads and deletes can be accomplished by adding or removing
       partitions, if that requirement is planned into the partitioning design.
-      <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are
+      <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are
       both far faster than a bulk operation.
       These commands also entirely avoid the <command>VACUUM</command>
       overhead caused by a bulk <command>DELETE</>.
@@ -2845,12 +2845,15 @@ VALUES ('Albany', NULL, NULL, 'NY');
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> supports partitioning
-    via table inheritance.  Each partition must be created as a child
-    table of a single parent table.  The parent table itself is normally
-    empty; it exists just to represent the entire data set.  You should be
-    familiar with inheritance (see <xref linkend="ddl-inherit">) before
-    attempting to set up partitioning.
+    Currently, <productname>PostgreSQL</productname> provides a way to
+    specify the partition key of table along with two methods of partitioning
+    to choose from.  Individual partitions of a partitioned table are created
+    using separate <literal>CREATE TABLE</> commands where you must specify
+    the partition bound such that it does not overlap with any existing
+    partitions of the parent table.  The parent table itself is empty;
+    it exists just to represent the entire data set. See <xref
+    linkend="sql-createtable"> and <xref linkend="sql-createforeigntable">
+    for more details on the exact syntax to use for above mentioned commands.
    </para>
 
    <para>
@@ -2894,59 +2897,22 @@ VALUES ('Albany', NULL, NULL, 'NY');
      <orderedlist spacing="compact">
       <listitem>
        <para>
-        Create the <quote>master</quote> table, from which all of the
-        partitions will inherit.
+        Create the <quote>partitioned</quote> table.
        </para>
        <para>
         This table will contain no data.  Do not define any check
         constraints on this table, unless you intend them to
         be applied equally to all partitions.  There is no point
-        in defining any indexes or unique constraints on it, either.
+        in defining any indexes or unique constraints on it, either,
+        since the notion of global uniqueness is not yet implemented.
        </para>
       </listitem>
 
       <listitem>
        <para>
-        Create several <quote>child</quote> tables that each inherit from
-        the master table.  Normally, these tables will not add any columns
-        to the set inherited from the master.
-       </para>
-
-       <para>
-        We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables
-        (or, possibly, foreign tables).
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        Add table constraints to the partition tables to define the
-        allowed key values in each partition.
-       </para>
-
-       <para>
-        Typical examples would be:
-<programlisting>
-CHECK ( x = 1 )
-CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
-CHECK ( outletID &gt;= 100 AND outletID &lt; 200 )
-</programlisting>
-        Ensure that the constraints guarantee that there is no overlap
-        between the key values permitted in different partitions.  A common
-        mistake is to set up range constraints like:
-<programlisting>
-CHECK ( outletID BETWEEN 100 AND 200 )
-CHECK ( outletID BETWEEN 200 AND 300 )
-</programlisting>
-        This is wrong since it is not clear which partition the key value
-        200 belongs in.
-       </para>
-
-       <para>
-        Note that there is no difference in
-        syntax between range and list partitioning; those terms are
-        descriptive only.
+        Create several <quote>partitions</quote> of the above created
+        partitioned table.  Partitions are in every way normal
+        <productname>PostgreSQL</> tables (or, possibly, foreign tables).
        </para>
       </listitem>
 
@@ -2963,8 +2929,10 @@ CHECK ( outletID BETWEEN 200 AND 300 )
 
       <listitem>
        <para>
-        Optionally, define a trigger or rule to redirect data inserted into
-        the master table to the appropriate partition.
+        Note that a data row inserted into the master table will be mapped
+        to and stored in the appropriate partition.  If some row does not
+        fall within any of existing partitions, an error will be thrown.
+        You must create the missing partition explicitly.
        </para>
       </listitem>
 
@@ -2992,7 +2960,7 @@ CREATE TABLE measurement (
     logdate         date not null,
     peaktemp        int,
     unitsales       int
-);
+) PARTITION BY RANGE (logdate);
 </programlisting>
 
      We know that most queries will access just the last week's, month's or
@@ -3023,12 +2991,12 @@ CREATE TABLE measurement (
         Next we create one partition for each active month:
 
 <programlisting>
-CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
 ...
-CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
+CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
 </programlisting>
 
         Each of the partitions are complete tables in their own right,
@@ -3038,36 +3006,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement);
 
        <para>
         This solves one of our problems: deleting old data. Each
-        month, all we will need to do is perform a <command>DROP
-        TABLE</command> on the oldest child table and create a new
-        child table for the new month's data.
-       </para>
-      </listitem>
-
-      <listitem>
-       <para>
-        We must provide non-overlapping table constraints.  Rather than
-        just creating the partition tables as above, the table creation
-        script should really be:
-
-<programlisting>
-CREATE TABLE measurement_y2006m02 (
-    CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
-    CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
-    CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
-    CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
-    CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-) INHERITS (measurement);
-</programlisting>
+        month, all we will need to do is perform a <command>ALTER TABLE
+        measurement DETACH PARTITION</command> on the oldest child table
+        and create a new partition for the new month's data.
        </para>
       </listitem>
 
@@ -3076,110 +3017,19 @@ CREATE TABLE measurement_y2008m01 (
         We probably need indexes on the key columns too:
 
 <programlisting>
-CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
-CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
+CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate);
+CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate);
 ...
-CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
-CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
-CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);
+CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate);
+CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate);
+CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate);
 </programlisting>
 
         We choose not to add further indexes at this time.
        </para>
       </listitem>
-
-      <listitem>
-       <para>
-        We want our application to be able to say <literal>INSERT INTO
-        measurement ...</> and have the data be redirected into the
-        appropriate partition table.  We can arrange that by attaching
-        a suitable trigger function to the master table.
-        If data will be added only to the latest partition, we can
-        use a very simple trigger function:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        After creating the function, we create a trigger which
-        calls the trigger function:
-
-<programlisting>
-CREATE TRIGGER insert_measurement_trigger
-    BEFORE INSERT ON measurement
-    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();
-</programlisting>
-
-        We must redefine the trigger function each month so that it always
-        points to the current partition.  The trigger definition does
-        not need to be updated, however.
-       </para>
-
-       <para>
-        We might want to insert data and have the server automatically
-        locate the partition into which the row should be added. We
-        could do this with a more complex trigger function, for example:
-
-<programlisting>
-CREATE OR REPLACE FUNCTION measurement_insert_trigger()
-RETURNS TRIGGER AS $$
-BEGIN
-    IF ( NEW.logdate &gt;= DATE '2006-02-01' AND
-         NEW.logdate &lt; DATE '2006-03-01' ) THEN
-        INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-    ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND
-            NEW.logdate &lt; DATE '2006-04-01' ) THEN
-        INSERT INTO measurement_y2006m03 VALUES (NEW.*);
-    ...
-    ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND
-            NEW.logdate &lt; DATE '2008-02-01' ) THEN
-        INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-    ELSE
-        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
-    END IF;
-    RETURN NULL;
-END;
-$$
-LANGUAGE plpgsql;
-</programlisting>
-
-        The trigger definition is the same as before.
-        Note that each <literal>IF</literal> test must exactly match the
-        <literal>CHECK</literal> constraint for its partition.
-       </para>
-
-       <para>
-        While this function is more complex than the single-month case,
-        it doesn't need to be updated as often, since branches can be
-        added in advance of being needed.
-       </para>
-
-       <note>
-        <para>
-         In practice it might be best to check the newest partition first,
-         if most inserts go into that partition.  For simplicity we have
-         shown the trigger's tests in the same order as in other parts
-         of this example.
-        </para>
-       </note>
-      </listitem>
      </orderedlist>
     </para>
-
-    <para>
-     As we can see, a complex partitioning scheme could require a
-     substantial amount of DDL. In the above example we would be
-     creating a new partition each month, so it might be wise to write a
-     script that generates the required DDL automatically.
-    </para>
-
    </sect2>
 
    <sect2 id="ddl-partitioning-managing-partitions">
@@ -3197,22 +3047,17 @@ LANGUAGE plpgsql;
    </para>
 
    <para>
-     The simplest option for removing old data is simply to drop the partition
+     The simplest option for removing old data is simply detach the partition
      that is no longer necessary:
 <programlisting>
-DROP TABLE measurement_y2006m02;
+ALTER TABLE measurement DETACH PARTITION measurement_y2016m07;
 </programlisting>
+
      This can very quickly delete millions of records because it doesn't have
      to individually delete every record.
-   </para>
 
-   <para>
-     Another option that is often preferable is to remove the partition from
-     the partitioned table but retain access to it as a table in its own
-     right:
-<programlisting>
-ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
-</programlisting>
+     The detached partition continues to exist as a regular table, which if
+     necessary can be dropped using regular <command>DROP TABLE</> command.
      This allows further operations to be performed on the data before
      it is dropped. For example, this is often a useful time to back up
      the data using <command>COPY</>, <application>pg_dump</>, or
@@ -3227,9 +3072,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
      were created above:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02 (
-    CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
 
      As an alternative, it is sometimes more convenient to create the
@@ -3238,13 +3081,15 @@ CREATE TABLE measurement_y2008m02 (
      transformed prior to it appearing in the partitioned table:
 
 <programlisting>
-CREATE TABLE measurement_y2008m02
+CREATE TABLE measurement_y2017m07
   (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
-ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02
-   CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+  CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' );
+\copy measurement_y2017m07 from 'measurement_y2017m07'
+ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07;
 -- possibly some other data preparation work
-ALTER TABLE measurement_y2008m02 INHERIT measurement;
+ALTER TABLE measurement
+  ATTACH PARTITION measurement_y2017m07 FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
 </programlisting>
     </para>
    </sect2>
@@ -3263,7 +3108,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
 <programlisting>
 SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01';
 </programlisting>
 
     Without constraint exclusion, the above query would scan each of
@@ -3272,7 +3117,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
     partition and try to prove that the partition need not
     be scanned because it could not contain any rows meeting the query's
     <literal>WHERE</> clause.  When the planner can prove this, it
-    excludes the partition from the query plan.
+    excludes the partition from the query plan.  Note that the aforementioned
+    constraints need not be explicitly created; they are internally derived
+    from the partition bound metadata.
    </para>
 
    <para>
@@ -3282,23 +3129,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=158.66..158.68 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..151.88 rows=2715 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m02 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2006m03 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=866.69..866.70 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..828.12 rows=15426 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2016m08  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 ...
-         -&gt;  Seq Scan on measurement_y2007m12 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m06  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2017-01-01'::date)
 </programlisting>
 
     Some or all of the partitions might use index scans instead of
@@ -3309,15 +3156,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
 <programlisting>
 SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
-                                          QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate  (cost=63.47..63.48 rows=1 width=0)
-   -&gt;  Append  (cost=0.00..60.75 rows=1086 width=0)
-         -&gt;  Seq Scan on measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
-         -&gt;  Seq Scan on measurement_y2008m01 measurement  (cost=0.00..30.38 rows=543 width=0)
-               Filter: (logdate &gt;= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01';
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate  (cost=34.67..34.68 rows=1 width=8)
+   -&gt;  Append  (cost=0.00..33.12 rows=618 width=0)
+         -&gt;  Seq Scan on measurement  (cost=0.00..0.00 rows=1 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
+         -&gt;  Seq Scan on measurement_y2018m07  (cost=0.00..33.12 rows=617 width=0)
+               Filter: (logdate &gt;= '2018-07-01'::date)
 </programlisting>
    </para>
 
@@ -3344,93 +3191,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    </sect2>
 
-   <sect2 id="ddl-partitioning-alternatives">
-   <title>Alternative Partitioning Methods</title>
-
-    <para>
-     A different approach to redirecting inserts into the appropriate
-     partition table is to set up rules, instead of a trigger, on the
-     master table.  For example:
-
-<programlisting>
-CREATE RULE measurement_insert_y2006m02 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
-    ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' )
-DO INSTEAD
-    INSERT INTO measurement_y2008m01 VALUES (NEW.*);
-</programlisting>
-
-     A rule has significantly more overhead than a trigger, but the overhead
-     is paid once per query rather than once per row, so this method might be
-     advantageous for bulk-insert situations.  In most cases, however, the
-     trigger method will offer better performance.
-    </para>
-
-    <para>
-     Be aware that <command>COPY</> ignores rules.  If you want to
-     use <command>COPY</> to insert data, you'll need to copy into the correct
-     partition table rather than into the master.  <command>COPY</> does fire
-     triggers, so you can use it normally if you use the trigger approach.
-    </para>
-
-    <para>
-     Another disadvantage of the rule approach is that there is no simple
-     way to force an error if the set of rules doesn't cover the insertion
-     date; the data will silently go into the master table instead.
-    </para>
-
-    <para>
-     Partitioning can also be arranged using a <literal>UNION ALL</literal>
-     view, instead of table inheritance.  For example,
-
-<programlisting>
-CREATE VIEW measurement AS
-          SELECT * FROM measurement_y2006m02
-UNION ALL SELECT * FROM measurement_y2006m03
-...
-UNION ALL SELECT * FROM measurement_y2007m11
-UNION ALL SELECT * FROM measurement_y2007m12
-UNION ALL SELECT * FROM measurement_y2008m01;
-</programlisting>
-
-     However, the need to recreate the view adds an extra step to adding and
-     dropping individual partitions of the data set.  In practice this
-     method has little to recommend it compared to using inheritance.
-    </para>
-
-   </sect2>
-
    <sect2 id="ddl-partitioning-caveats">
    <title>Caveats</title>
 
    <para>
     The following caveats apply to partitioned tables:
    <itemizedlist>
-    <listitem>
-     <para>
-      There is no automatic way to verify that all of the
-      <literal>CHECK</literal> constraints are mutually
-      exclusive.  It is safer to create code that generates
-      partitions and creates and/or modifies associated objects than
-      to write each by hand.
-     </para>
-    </listitem>
 
     <listitem>
      <para>
       The schemes shown here assume that the partition key column(s)
       of a row never change, or at least do not change enough to require
       it to move to another partition.  An <command>UPDATE</> that attempts
-      to do that will fail because of the <literal>CHECK</> constraints.
-      If you need to handle such cases, you can put suitable update triggers
-      on the partition tables, but it makes management of the structure
-      much more complicated.
+      to do that will fail because of applying internally created <literal>CHECK</>
+      constraints.  If you need to handle such cases, you can put suitable
+      update triggers on the partition tables, but it makes management of the
+      structure much more complicated.
      </para>
     </listitem>
 
@@ -3449,9 +3225,9 @@ ANALYZE measurement;
     <listitem>
      <para>
       <command>INSERT</command> statements with <literal>ON CONFLICT</>
-      clauses are unlikely to work as expected, as the <literal>ON CONFLICT</>
-      action is only taken in case of unique violations on the specified
-      target relation, not its child relations.
+      clauses are currently unsupported on partitioned tables as there is
+      currently no reliable way to check global uniqueness across all the
+      partitions.
      </para>
     </listitem>
 
@@ -3475,18 +3251,6 @@ ANALYZE measurement;
 
     <listitem>
      <para>
-      Keep the partitioning constraints simple, else the planner may not be
-      able to prove that partitions don't need to be visited.  Use simple
-      equality conditions for list partitioning, or simple
-      range tests for range partitioning, as illustrated in the preceding
-      examples.  A good rule of thumb is that partitioning constraints should
-      contain only comparisons of the partitioning column(s) to constants
-      using B-tree-indexable operators.
-     </para>
-    </listitem>
-
-    <listitem>
-     <para>
       All constraints on all partitions of the master table are examined
       during constraint exclusion, so large numbers of partitions are likely
       to increase query planning time considerably.  Partitioning using
-- 
2.11.0

#165Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#164)
Re: Declarative partitioning - another take

On Wed, Dec 7, 2016 at 6:42 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/07 13:38, Robert Haas wrote:

On Wed, Nov 30, 2016 at 10:56 AM, Amit Langote <amitlangote09@gmail.com> wrote:

The latest patch I posted earlier today has this implementation.

I decided to try out these patches today with #define
CLOBBER_CACHE_ALWAYS 1 in pg_config_manual.h, which found a couple of
problems:

1. RelationClearRelation() wasn't preserving the rd_partkey, even
though there's plenty of code that relies on it not changing while we
hold a lock on the relation - in particular, transformPartitionBound.

Oh, I thought an AccessExclusiveLock on the relation would prevent having
to worry about that, but guess I'm wrong. Perhaps, having a lock on a
table does not preclude RelationClearRelation() resetting the table's
relcache.

No, it sure doesn't. The lock prevents the table from actually being
changed, so a reload will find data equivalent to what it had before,
but it doesn't prevent the backend's cache from being flushed.

2. partition_bounds_equal() was using the comparator and collation for
partitioning column 0 to compare the datums for all partitioning
columns. It's amazing this passed the regression tests.

Oops, it seems that the regression tests where the above code might be
exercised consisted only of range partition key with columns all of the
same type: create table test(a int, b int) partition by range (a, (a+b));

It doesn't seem like it; you had this: create table part1 partition of
range_parted for values from ('a', 1) to ('a', 10);

I recommend that once you fix this, you run 'make check' with #define
CLOBBER_CACHE_ALWAYS 1 and look for other hazards. Such mistakes are
easy to make with this kind of patch.

With the attached latest version of the patches, I couldn't see any
failures with a CLOBBER_CACHE_ALWAYS build.

Cool.

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

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

#166Erik Rijkers
er@xs4all.nl
In reply to: Amit Langote (#164)
Re: Declarative partitioning - another take

On 2016-12-07 12:42, Amit Langote wrote:

0001-Catalog-and-DDL-for-partitioned-tables-20.patch
0002-psql-and-pg_dump-support-for-partitioned-tables-20.patch
0003-Catalog-and-DDL-for-partitions-20.patch
0004-psql-and-pg_dump-support-for-partitions-20.patch
0005-Teach-a-few-places-to-use-partition-check-quals-20.patch
0006-Tuple-routing-for-partitioned-tables-20.patch
0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patch

Patches apply, compile, check OK.

But this yields a segfault:

begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );

it logs as follows:

2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running:
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );
2016-12-07 17:03:45.787 CET 6125 LOG: terminating any other active
server processes
2016-12-07 17:03:45.791 CET 6125 LOG: all server processes terminated;
reinitializing
2016-12-07 17:03:45.999 CET 11655 LOG: database system was interrupted;
last known up at 2016-12-07 17:00:38 CET
2016-12-07 17:03:48.040 CET 11655 LOG: database system was not properly
shut down; automatic recovery in progress
2016-12-07 17:03:48.156 CET 11655 LOG: redo starts at 0/2897988
2016-12-07 17:03:48.172 CET 11655 LOG: invalid magic number 0000 in log
segment 000000010000000000000002, offset 9207808
2016-12-07 17:03:48.172 CET 11655 LOG: redo done at 0/28C72C0
2016-12-07 17:03:48.172 CET 11655 LOG: last completed transaction was
at log time 2016-12-07 17:01:29.580562+01
2016-12-07 17:03:49.534 CET 11655 LOG: MultiXact member wraparound
protections are now enabled
2016-12-07 17:03:49.622 CET 6125 LOG: database system is ready to
accept connections

Thanks,

Erik Rijkers

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

#167Amit Langote
amitlangote09@gmail.com
In reply to: Erik Rijkers (#166)
Re: Declarative partitioning - another take

Hi Erik,

On Thu, Dec 8, 2016 at 1:19 AM, Erik Rijkers <er@xs4all.nl> wrote:

On 2016-12-07 12:42, Amit Langote wrote:

0001-Catalog-and-DDL-for-partitioned-tables-20.patch
0002-psql-and-pg_dump-support-for-partitioned-tables-20.patch
0003-Catalog-and-DDL-for-partitions-20.patch
0004-psql-and-pg_dump-support-for-partitions-20.patch
0005-Teach-a-few-places-to-use-partition-check-quals-20.patch
0006-Tuple-routing-for-partitioned-tables-20.patch
0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patch

Patches apply, compile, check OK.

Thanks!

But this yields a segfault:

begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );

it logs as follows:

2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running: create
table s.t_part_ascii_065 partition of s.t for values in ( 65 );

Hm, will look into this in a few hours.

Thanks,
Amit

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

#168Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#167)
1 attachment(s)
Re: Declarative partitioning - another take

On Wed, Dec 7, 2016 at 11:34 AM, Amit Langote <amitlangote09@gmail.com> wrote:

begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );

it logs as follows:

2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running: create
table s.t_part_ascii_065 partition of s.t for values in ( 65 );

Hm, will look into this in a few hours.

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

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

Attachments:

partkey-flush-fix.patchtext/x-patch; charset=US-ASCII; name=partkey-flush-fix.patchDownload
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ae77d15..a230b20 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2516,6 +2516,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partkey;
 		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
@@ -2547,6 +2548,7 @@ 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_partkey = (relation->rd_partkey != NULL);
 		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
 											relation->rd_partdesc,
 											newrel->rd_partdesc);
@@ -2604,9 +2606,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-		/* partition key must be preserved */
-		SWAPFIELD(PartitionKey, rd_partkey);
-		SWAPFIELD(MemoryContext, rd_partkeycxt);
+		/* partition key must be preserved, if we have one */
+		if (keep_partkey)
+		{
+			SWAPFIELD(PartitionKey, rd_partkey);
+			SWAPFIELD(MemoryContext, rd_partkeycxt);
+		}
 		/* preserve old partdesc if no logical change */
 		if (keep_partdesc)
 		{
#169Erik Rijkers
er@xs4all.nl
In reply to: Robert Haas (#168)
Re: Declarative partitioning - another take

On 2016-12-07 17:38, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:34 AM, Amit Langote <amitlangote09@gmail.com>
wrote:

begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65
);

it logs as follows:

2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running:
create
table s.t_part_ascii_065 partition of s.t for values in ( 65 );

Hm, will look into this in a few hours.

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

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

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

#170Robert Haas
robertmhaas@gmail.com
In reply to: Erik Rijkers (#169)
Re: Declarative partitioning - another take

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

Some notes:

* We should try to teach the executor never to scan the parent.
That's never necessary with this system, and it might add significant
overhead. We should also try to get rid of the idea of the parent
having storage (i.e. a relfilenode).

* The fact that, in some cases, locking requirements for partitioning
are stronger than those for inheritance is not good. We made those
decisions for good reasons -- namely, data integrity and not crashing
the server -- but it would certainly be good to revisit those things
and see whether there's any room for improvement.

* I didn't commit 0007, which updates the documentation for this new
feature. That patch removes more lines than it adds, and I suspect
what is needed here
is an expansion of the documentation rather than a diminishing of it.

* The fact that there's no implementation of row movement should be
documented as a limitation. We should also look at removing that
limitation.

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

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

#171Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Erik Rijkers (#169)
Re: Declarative partitioning - another take

On 2016/12/08 1:53, Erik Rijkers wrote:

On 2016-12-07 17:38, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:34 AM, Amit Langote <amitlangote09@gmail.com>
wrote:

begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );

it logs as follows:

2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running:
create
table s.t_part_ascii_065 partition of s.t for values in ( 65 );

Hm, will look into this in a few hours.

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for reporting and the fix, Erik and Robert!

Thanks,
Amit

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

#172Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#170)
Re: Declarative partitioning - another take

Hi Robert,

On 2016/12/08 3:20, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

+1 and thanks a lot for your and everyone else's very patient support in
reviewing the patches.

Some notes:

* We should try to teach the executor never to scan the parent.
That's never necessary with this system, and it might add significant
overhead. We should also try to get rid of the idea of the parent
having storage (i.e. a relfilenode).

Agreed, I will start investigating.

* The fact that, in some cases, locking requirements for partitioning
are stronger than those for inheritance is not good. We made those
decisions for good reasons -- namely, data integrity and not crashing
the server -- but it would certainly be good to revisit those things
and see whether there's any room for improvement.

+1

* I didn't commit 0007, which updates the documentation for this new
feature. That patch removes more lines than it adds, and I suspect
what is needed here
is an expansion of the documentation rather than a diminishing of it.

Hmm, I had mixed feeling about what to do about that as well. So now, we
have the description of various new features buried into VI. Reference
section of the documentation, which is simply meant as a command
reference. I agree that the new partitioning warrants more expansion in
the DDL partitioning chapter. Will see how that could be done.

* The fact that there's no implementation of row movement should be
documented as a limitation. We should also look at removing that
limitation.

Yes, something to improve. By the way, since we currently mention INSERT
tuple-routing directly in the description of the partitioned tables in the
CREATE TABLE command reference, is that also the place to list this
particular limitation? Or is UPDATE command reference rather the correct
place?

Thanks,
Amit

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

#173Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#170)
Re: Declarative partitioning - another take

On 2016-12-07 13:20:04 -0500, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

Congrats to everyone working on this! This is a large step forward.

- Andres

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

#174Michael Paquier
michael.paquier@gmail.com
In reply to: Andres Freund (#173)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 1:39 PM, Andres Freund <andres@anarazel.de> wrote:

On 2016-12-07 13:20:04 -0500, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

Congrats to everyone working on this! This is a large step forward.

Congratulations to all! It was a long way to this result.
--
Michael

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

#175Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#170)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/08 3:20, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

Attached is a patch to fix some stale comments in the code and a minor
correction to one of the examples on the CREATE TABLE page.

Thanks,
Amit

Attachments:

misc-comment-doc-fixes.patchtext/x-diff; name=misc-comment-doc-fixes.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8bf8af302b..bb6cffabf7 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1498,7 +1498,7 @@ CREATE TABLE cities (
 <programlisting>
 CREATE TABLE measurement_y2016m07
     PARTITION OF measurement (
-    unitsales WITH OPTIONS DEFAULT 0
+    unitsales DEFAULT 0
 ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
 </programlisting></para>
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6dab45f0ed..441b31c46e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1492,7 +1492,7 @@ generate_partition_qual(Relation rel, bool recurse)
  *			Construct values[] and isnull[] arrays for the partition key
  *			of a tuple.
  *
- *	pkinfo			partition key execution info
+ *	pd				Partition dispatch object of the partitioned table
  *	slot			Heap tuple from which to extract partition key
  *	estate			executor state for evaluating any partition key
  *					expressions (must be non-NULL)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 270be0af18..2d2d383941 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1403,10 +1403,7 @@ BeginCopy(ParseState *pstate,
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
 
-		/*
-		 * Initialize state for CopyFrom tuple routing.  Watch out for
-		 * any foreign partitions.
-		 */
+		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			PartitionDispatch *pd;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c77b216d4f..917795af9c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1614,8 +1614,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * In case of a partition, there are no new column definitions, only
-	 * dummy ColumnDefs created for column constraints.  We merge these
-	 * constraints inherited from the parent.
+	 * dummy ColumnDefs created for column constraints.  We merge them
+	 * with the constraints inherited from the parent.
 	 */
 	if (is_partition)
 	{
@@ -2030,8 +2030,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
-				 * Partitions have only one parent, so conflict should never
-				 * occur
+				 * Partitions have only one parent and have no column
+				 * definitions of their own, so conflict should never occur.
 				 */
 				Assert(!is_partition);
 
@@ -2118,8 +2118,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * Now that we have the column definition list for a partition, we can
-	 * check whether the columns referenced in column option specifications
-	 * actually exist.  Also, we merge the options into the corresponding
+	 * check whether the columns referenced in the column constraint specs
+	 * actually exist.  Also, we merge the constraints into the corresponding
 	 * column definitions.
 	 */
 	if (is_partition && list_length(saved_schema) > 0)
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index cec54ae62e..be8727b556 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -47,7 +47,7 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 #ifdef CATALOG_VARLEN
 	oidvector		partclass;		/* operator class to compare keys */
 	oidvector		partcollation;	/* user-specified collation for keys */
-	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+	pg_node_tree	partexprs;		/* list of expressions in the partition
 									 * key; one item for each zero entry in
 									 * partattrs[] */
 #endif
#176Stephen Frost
sfrost@snowman.net
In reply to: Amit Langote (#172)
Re: Declarative partitioning - another take

Amit,

* Amit Langote (Langote_Amit_f8@lab.ntt.co.jp) wrote:

Hmm, I had mixed feeling about what to do about that as well. So now, we
have the description of various new features buried into VI. Reference
section of the documentation, which is simply meant as a command
reference. I agree that the new partitioning warrants more expansion in
the DDL partitioning chapter. Will see how that could be done.

Definitely.

* The fact that there's no implementation of row movement should be
documented as a limitation. We should also look at removing that
limitation.

Yes, something to improve. By the way, since we currently mention INSERT
tuple-routing directly in the description of the partitioned tables in the
CREATE TABLE command reference, is that also the place to list this
particular limitation? Or is UPDATE command reference rather the correct
place?

Both.

Thanks!

Stephen

#177Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#174)
Re: Declarative partitioning - another take

On Wed, Dec 7, 2016 at 11:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Congrats to everyone working on this! This is a large step forward.

Congratulations to all! It was a long way to this result.

Yes. The last effort in this area which I can remember was by Itagaki
Takahiro in 2010, so we've been waiting for this for more than 6
years. It's really good that Amit was able to put in the effort to
produce a committable patch, and I think he deserves all of our thanks
for getting that done - and NTT deserves our thanks for paying him to
do it.

Even though I know he put in a lot more work than I did, let me just
say: phew, even reviewing that was a ton of work.

Of course, this is the beginning, not the end. I've been thinking
about next steps -- here's an expanded list:

- more efficient plan-time partition pruning (constraint exclusion is too slow)
- run-time partition pruning
- partition-wise join (Ashutosh Bapat is already working on this)
- try to reduce lock levels
- hash partitioning
- the ability to create an index on the parent and have all of the
children inherit it; this should work something like constraint
inheritance. you could argue that this doesn't add any real new
capability but it's a huge usability feature.
- teaching autovacuum enough about inheritance hierarchies for it to
update the parent statistics when they get stale despite the lack of
any actual inserts/updates/deletes to the parent. this has been
pending for a long time, but it's only going to get more important
- row movement (aka avoiding the need for an ON UPDATE trigger on each
partition)
- insert (and eventually update) tuple routing for foreign partitions
- not scanning the parent
- fixing the insert routing so that we can skip tuple conversion where possible
- fleshing out the documentation

One thing I'm wondering is whether we can optimize away some of the
heavyweight locks. For example, if somebody does SELECT * FROM ptab
WHERE id = 1, they really shouldn't need to lock the entire
partitioning hierarchy, but right now they do. If the root knows
based on its own partitioning key that only one child is relevant, it
would be good to lock *only that child*. For this feature to be
competitive, it needs to scale to at least a few thousand partitions,
and locking thousands of objects instead of one or two is bound to be
slow. Similarly, you can imagine teaching COPY to lock partitions
only on demand; if no tuples are routed to a particular partition, we
don't need to lock it. There's a manageability component here, too:
not locking partitions unnecessarily makes ti easier to get DDL on
other partitions through. Alternatively, maybe we could rewrite the
lock manager to be hierarchical, so that you can take a single lock
that represents an AccessShareLock on all partitions and only need to
make one entry in the lock table to do it. That means that attempts
to lock individual partitions need to check not only for a lock on
that partition but also on anything further up in the hierarchy, but
that might be a good trade if it gives us O(1) locking on the parent.
And maybe we could also have a level of the hierarchy that represents
every-table-in-the-database, for the benefit of pg_dump. Of course,
rewriting the lock manager is a big project not for the faint of
heart, but I think if we don't it's going to be a scaling bottleneck.

We also need to consider other parts of the system that may not scale,
like pg_dump. For a long time, we've been sorta-kinda willing to fix
the worst of the scalability problems with pg_dump, but that's really
no longer an adequate response. People want 1000 partitions. Heck,
people want 1,000,000 partitions, but getting to where 1000 partitions
works well would help PostgreSQL a lot. Our oft-repeated line that
inheritance isn't designed for large numbers of inheritance children
is basically just telling people who have the use case where they need
that to go use some other product. Partitioning, like replication, is
not an optional feature for a world-class database. And, from a
technical point of view, I think we've now got an infrastructure that
really should be able to be scaled up considerably higher than what
we've been able to do in the past. When we were stuck with
inheritance + constraint exclusion, we could say "well, there's not
really any point because you'll hit these other limits anyway". But I
think now that's not really true. This patch eliminates one of the
core scalability problems in this area, and provides infrastructure
for attacking some of the others. I hope that people will step up and
do that. There's a huge opportunity here for PostgreSQL to become
relevant in use cases where it currently falters badly, and we should
try to take advantage of it. This patch is a big step by itself, but
if we ignore the potential to do more with this as the base we will be
leaving a lot of "win" on the table.

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

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

#178Dmitry Ivanov
d.ivanov@postgrespro.ru
In reply to: Robert Haas (#177)
Re: Declarative partitioning - another take

Hi everyone,

Of course, this is the beginning, not the end. I've been thinking
about next steps -- here's an expanded list:

- more efficient plan-time partition pruning (constraint
exclusion is too slow)
- run-time partition pruning
- try to reduce lock levels
...

We (PostgresPro) have been working on pg_pathman for quite a while, and
since it's obviously going to become the thing of the past, it would be a
wasted effort if we didn't try to participate.

For starters, I'd love to work on both plan-time & run-time partition
pruning. I created a custom node for run-time partition elimination, so I
think I'm capable of developing something similar.

--
Dmitry Ivanov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

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

#179Robert Haas
robertmhaas@gmail.com
In reply to: Dmitry Ivanov (#178)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 11:13 AM, Dmitry Ivanov <d.ivanov@postgrespro.ru> wrote:

We (PostgresPro) have been working on pg_pathman for quite a while, and
since it's obviously going to become the thing of the past, it would be a
wasted effort if we didn't try to participate.

For starters, I'd love to work on both plan-time & run-time partition
pruning. I created a custom node for run-time partition elimination, so I
think I'm capable of developing something similar.

That would be fantastic. I and my colleagues at EnterpriseDB can
surely help review; of course, maybe you and some of your colleagues
would like to help review our patches, too. Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10? Time is growing short, but it would
be great to polish this a little more before we ship it.

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

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

#180Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#179)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 7:29 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Dec 8, 2016 at 11:13 AM, Dmitry Ivanov <d.ivanov@postgrespro.ru>
wrote:

We (PostgresPro) have been working on pg_pathman for quite a while, and
since it's obviously going to become the thing of the past, it would be a
wasted effort if we didn't try to participate.

For starters, I'd love to work on both plan-time & run-time partition
pruning. I created a custom node for run-time partition elimination, so I
think I'm capable of developing something similar.

That would be fantastic. I and my colleagues at EnterpriseDB can

surely help review;

Great! And it is very cool that we have basic infrastructure already
committed. Thanks a lot to you and everybody involved.

of course, maybe you and some of your colleagues
would like to help review our patches, too.

We understand our reviewing performance is not sufficient. Will try to do
better during next commitfest.

Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10?

Yes, because we have set of features already implemented in pg_pathman. In
particular we have following features from your list and some more.

- more efficient plan-time partition pruning (constraint exclusion is too
slow)
- run-time partition pruning
- insert (and eventually update) tuple routing for foreign partitions
- hash partitioning
- not scanning the parent

Time is growing short, but it would

be great to polish this a little more before we ship it.

Yes. Getting at least some of this features committed to v10 would be great
and improve partitioning usability a lot.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#181Dmitry Ivanov
d.ivanov@postgrespro.ru
In reply to: Robert Haas (#179)
Re: Declarative partitioning - another take

That would be fantastic. I and my colleagues at EnterpriseDB can
surely help review; of course, maybe you and some of your colleagues
would like to help review our patches, too.

Certainly, I'll start reviewing as soon as I get familiar with the code.

Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10?

Yes, I've just cleared my schedule in order to make this possible. I'll
bring in the patches ASAP.

--
Dmitry Ivanov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

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

#182Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#180)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 11:43 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Great! And it is very cool that we have basic infrastructure already
committed. Thanks a lot to you and everybody involved.

Thanks.

of course, maybe you and some of your colleagues
would like to help review our patches, too.

We understand our reviewing performance is not sufficient. Will try to do
better during next commitfest.

Not trying to throw stones, just want to get as much committed as
possible. And I think our patches are good and valuable improvements
too, so I want to see them go in because they will help everybody.
Thanks for trying to increase the reviewing effort; it is sorely
needed.

Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10?

Yes, because we have set of features already implemented in pg_pathman. In
particular we have following features from your list and some more.

- more efficient plan-time partition pruning (constraint exclusion is too
slow)
- run-time partition pruning
- insert (and eventually update) tuple routing for foreign partitions
- hash partitioning
- not scanning the parent

That's a lot of stuff. Getting even a couple of those would be a big win.

Time is growing short, but it would
be great to polish this a little more before we ship it.

Yes. Getting at least some of this features committed to v10 would be great
and improve partitioning usability a lot.

+1.

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

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

#183Robert Haas
robertmhaas@gmail.com
In reply to: Dmitry Ivanov (#181)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 11:44 AM, Dmitry Ivanov <d.ivanov@postgrespro.ru> wrote:

That would be fantastic. I and my colleagues at EnterpriseDB can
surely help review; of course, maybe you and some of your colleagues
would like to help review our patches, too.

Certainly, I'll start reviewing as soon as I get familiar with the code.

Thanks!

Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10?

Yes, I've just cleared my schedule in order to make this possible. I'll
bring in the patches ASAP.

Fantastic.

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

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

#184Tsunakawa, Takayuki
tsunakawa.takay@jp.fujitsu.com
In reply to: Alexander Korotkov (#180)
Re: Declarative partitioning - another take

From: pgsql-hackers-owner@postgresql.org

[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Alexander
Korotkov
Yes. Getting at least some of this features committed to v10 would be great
and improve partitioning usability a lot.

I'm sorry for not contributing to the real partitioning feature, but I'm really looking forward to seeing the efficient plan-time and run-time partition pruning implemented in v10. Recently, we failed to acquire a customer because they could not achieve their performance goal due to the slow partition pruning compared to Oracle. The batch app prepares a SELECT statement against a partitioned table, then executes it millions of time with different parameter values. It took a long time to process Bind messages.

Another requirement was subpartitioning. Will this be possible with the current infrastructure, or does this need drastic change?

Regards
Takayuki Tsunakawa

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

#185Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Tsunakawa, Takayuki (#184)
Re: Declarative partitioning - another take

On 2016/12/09 10:09, Tsunakawa, Takayuki wrote:

Another requirement was subpartitioning. Will this be possible with the current infrastructure, or does this need drastic change?

It does support sub-partitioning, although the syntax is a bit different.

Thanks,
Amit

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

#186Tsunakawa, Takayuki
tsunakawa.takay@jp.fujitsu.com
In reply to: Amit Langote (#185)
Re: Declarative partitioning - another take

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]

On 2016/12/09 10:09, Tsunakawa, Takayuki wrote:

Another requirement was subpartitioning. Will this be possible with the

current infrastructure, or does this need drastic change?

It does support sub-partitioning, although the syntax is a bit different.

Super great! I'm excited to try the feature when I have time. I hope I can contribute to the quality by find any bug.

Regards
Takayuki Tsunakawa

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

#187Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Stephen Frost (#176)
1 attachment(s)
Re: Declarative partitioning - another take

Hi Stephen,

On 2016/12/08 22:35, Stephen Frost wrote:

* The fact that there's no implementation of row movement should be
documented as a limitation. We should also look at removing that
limitation.

Yes, something to improve. By the way, since we currently mention INSERT
tuple-routing directly in the description of the partitioned tables in the
CREATE TABLE command reference, is that also the place to list this
particular limitation? Or is UPDATE command reference rather the correct
place?

Both.

Attached a documentation fix patch.

Actually, there was no mention on the INSERT reference page of
tuple-routing occurring in case of partitioned tables and also the
possibility of an error if a *partition* is directly targeted in an
INSERT. Mentioned that as well.

Thanks,
Amit

Attachments:

partitioned-table-ins-upd-doc-fixes-1.patchtext/x-diff; name=partitioned-table-ins-upd-doc-fixes-1.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8bf8af302b..01abe71f84 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -275,7 +275,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      <para>
       Rows inserted into a partitioned table will be automatically routed to
       the correct partition.  If no suitable partition exists, an error will
-      occur.
+      occur.  Also, if updating a row in a given partition causes it to move
+      to another partition due to the new partition key, an error will occur.
      </para>
 
      <para>
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index 06f416039b..00c984d8d5 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -526,6 +526,17 @@ INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</repl
    updated by the command.
   </para>
  </refsect1>
+ 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   If the specified table is a partitioned table, each row is routed to
+   the appropriate partition and inserted into it.  If the specified table
+   is a partition, an error will occur if one of the input rows violates
+   the partition constraint.
+  </para>
+ </refsect1>
 
  <refsect1>
   <title>Examples</title>
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 2de0f4aad1..e86993b9cf 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -279,6 +279,13 @@ UPDATE <replaceable class="parameter">count</replaceable>
    sub-selects is safer, though often harder to read and slower than
    using a join.
   </para>
+
+  <para>
+   In case of partitioned tables, updating a row might cause it to move
+   to a new partition due to the new partition key.  An error will occur
+   in this case.  Also, if the specified table is a partition, an error
+   will occur if the new row violates the partition constraint.
+  </para>
  </refsect1>
 
  <refsect1>
#188Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#177)
Re: Declarative partitioning - another take

On 2016/12/09 0:25, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Congrats to everyone working on this! This is a large step forward.

Congratulations to all! It was a long way to this result.

Yes. The last effort in this area which I can remember was by Itagaki
Takahiro in 2010, so we've been waiting for this for more than 6
years. It's really good that Amit was able to put in the effort to
produce a committable patch, and I think he deserves all of our thanks
for getting that done - and NTT deserves our thanks for paying him to
do it.

Even though I know he put in a lot more work than I did, let me just
say: phew, even reviewing that was a ton of work.

Absolutely! Your review comments and design suggestions have been
instrumental in improving (and cutting down on the size of) the patches.

Of course, this is the beginning, not the end.

+1000!

I've been thinking
about next steps -- here's an expanded list:

- more efficient plan-time partition pruning (constraint exclusion is too slow)
- run-time partition pruning
- partition-wise join (Ashutosh Bapat is already working on this)
- try to reduce lock levels
- hash partitioning
- the ability to create an index on the parent and have all of the
children inherit it; this should work something like constraint
inheritance. you could argue that this doesn't add any real new
capability but it's a huge usability feature.
- teaching autovacuum enough about inheritance hierarchies for it to
update the parent statistics when they get stale despite the lack of
any actual inserts/updates/deletes to the parent. this has been
pending for a long time, but it's only going to get more important
- row movement (aka avoiding the need for an ON UPDATE trigger on each
partition)
- insert (and eventually update) tuple routing for foreign partitions
- not scanning the parent
- fixing the insert routing so that we can skip tuple conversion where possible
- fleshing out the documentation

I would definitely want to contribute to some of these items. It's great
that many others plan to contribute toward this as well.

One thing I'm wondering is whether we can optimize away some of the
heavyweight locks. For example, if somebody does SELECT * FROM ptab
WHERE id = 1, they really shouldn't need to lock the entire
partitioning hierarchy, but right now they do. If the root knows
based on its own partitioning key that only one child is relevant, it
would be good to lock *only that child*. For this feature to be
competitive, it needs to scale to at least a few thousand partitions,
and locking thousands of objects instead of one or two is bound to be
slow. Similarly, you can imagine teaching COPY to lock partitions
only on demand; if no tuples are routed to a particular partition, we
don't need to lock it. There's a manageability component here, too:
not locking partitions unnecessarily makes ti easier to get DDL on
other partitions through. Alternatively, maybe we could rewrite the
lock manager to be hierarchical, so that you can take a single lock
that represents an AccessShareLock on all partitions and only need to
make one entry in the lock table to do it. That means that attempts
to lock individual partitions need to check not only for a lock on
that partition but also on anything further up in the hierarchy, but
that might be a good trade if it gives us O(1) locking on the parent.
And maybe we could also have a level of the hierarchy that represents
every-table-in-the-database, for the benefit of pg_dump. Of course,
rewriting the lock manager is a big project not for the faint of
heart, but I think if we don't it's going to be a scaling bottleneck.

Hierarchical lock manager stuff is interesting. Are you perhaps alluding
to a new *intention* lock mode as described in the literature on multiple
granularity locking [1]https://en.wikipedia.org/wiki/Multiple_granularity_locking?

We also need to consider other parts of the system that may not scale,
like pg_dump. For a long time, we've been sorta-kinda willing to fix
the worst of the scalability problems with pg_dump, but that's really
no longer an adequate response. People want 1000 partitions. Heck,
people want 1,000,000 partitions, but getting to where 1000 partitions
works well would help PostgreSQL a lot. Our oft-repeated line that
inheritance isn't designed for large numbers of inheritance children
is basically just telling people who have the use case where they need
that to go use some other product. Partitioning, like replication, is
not an optional feature for a world-class database. And, from a
technical point of view, I think we've now got an infrastructure that
really should be able to be scaled up considerably higher than what
we've been able to do in the past. When we were stuck with
inheritance + constraint exclusion, we could say "well, there's not
really any point because you'll hit these other limits anyway". But I
think now that's not really true. This patch eliminates one of the
core scalability problems in this area, and provides infrastructure
for attacking some of the others. I hope that people will step up and
do that. There's a huge opportunity here for PostgreSQL to become
relevant in use cases where it currently falters badly, and we should
try to take advantage of it. This patch is a big step by itself, but
if we ignore the potential to do more with this as the base we will be
leaving a lot of "win" on the table.

Agreed on all counts.

Thanks,
Amit

[1]: https://en.wikipedia.org/wiki/Multiple_granularity_locking

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

#189Venkata B Nagothi
nag1010@gmail.com
In reply to: Amit Langote (#188)
Re: Declarative partitioning - another take

Hi,

I am testing the partitioning feature from the latest master and got the
following error while loading the data -

db01=# create table orders_y1993 PARTITION OF orders FOR VALUES FROM
('1993-01-01') TO ('1993-12-31');
CREATE TABLE

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
*ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes*
*CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"*

Am i doing something wrong ?

Regards,

Venkata B N
Database Consultant

On Fri, Dec 9, 2016 at 3:58 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Show quoted text

On 2016/12/09 0:25, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Congrats to everyone working on this! This is a large step forward.

Congratulations to all! It was a long way to this result.

Yes. The last effort in this area which I can remember was by Itagaki
Takahiro in 2010, so we've been waiting for this for more than 6
years. It's really good that Amit was able to put in the effort to
produce a committable patch, and I think he deserves all of our thanks
for getting that done - and NTT deserves our thanks for paying him to
do it.

Even though I know he put in a lot more work than I did, let me just
say: phew, even reviewing that was a ton of work.

Absolutely! Your review comments and design suggestions have been
instrumental in improving (and cutting down on the size of) the patches.

Of course, this is the beginning, not the end.

+1000!

I've been thinking
about next steps -- here's an expanded list:

- more efficient plan-time partition pruning (constraint exclusion is

too slow)

- run-time partition pruning
- partition-wise join (Ashutosh Bapat is already working on this)
- try to reduce lock levels
- hash partitioning
- the ability to create an index on the parent and have all of the
children inherit it; this should work something like constraint
inheritance. you could argue that this doesn't add any real new
capability but it's a huge usability feature.
- teaching autovacuum enough about inheritance hierarchies for it to
update the parent statistics when they get stale despite the lack of
any actual inserts/updates/deletes to the parent. this has been
pending for a long time, but it's only going to get more important
- row movement (aka avoiding the need for an ON UPDATE trigger on each
partition)
- insert (and eventually update) tuple routing for foreign partitions
- not scanning the parent
- fixing the insert routing so that we can skip tuple conversion where

possible

- fleshing out the documentation

I would definitely want to contribute to some of these items. It's great
that many others plan to contribute toward this as well.

One thing I'm wondering is whether we can optimize away some of the
heavyweight locks. For example, if somebody does SELECT * FROM ptab
WHERE id = 1, they really shouldn't need to lock the entire
partitioning hierarchy, but right now they do. If the root knows
based on its own partitioning key that only one child is relevant, it
would be good to lock *only that child*. For this feature to be
competitive, it needs to scale to at least a few thousand partitions,
and locking thousands of objects instead of one or two is bound to be
slow. Similarly, you can imagine teaching COPY to lock partitions
only on demand; if no tuples are routed to a particular partition, we
don't need to lock it. There's a manageability component here, too:
not locking partitions unnecessarily makes ti easier to get DDL on
other partitions through. Alternatively, maybe we could rewrite the
lock manager to be hierarchical, so that you can take a single lock
that represents an AccessShareLock on all partitions and only need to
make one entry in the lock table to do it. That means that attempts
to lock individual partitions need to check not only for a lock on
that partition but also on anything further up in the hierarchy, but
that might be a good trade if it gives us O(1) locking on the parent.
And maybe we could also have a level of the hierarchy that represents
every-table-in-the-database, for the benefit of pg_dump. Of course,
rewriting the lock manager is a big project not for the faint of
heart, but I think if we don't it's going to be a scaling bottleneck.

Hierarchical lock manager stuff is interesting. Are you perhaps alluding
to a new *intention* lock mode as described in the literature on multiple
granularity locking [1]?

We also need to consider other parts of the system that may not scale,
like pg_dump. For a long time, we've been sorta-kinda willing to fix
the worst of the scalability problems with pg_dump, but that's really
no longer an adequate response. People want 1000 partitions. Heck,
people want 1,000,000 partitions, but getting to where 1000 partitions
works well would help PostgreSQL a lot. Our oft-repeated line that
inheritance isn't designed for large numbers of inheritance children
is basically just telling people who have the use case where they need
that to go use some other product. Partitioning, like replication, is
not an optional feature for a world-class database. And, from a
technical point of view, I think we've now got an infrastructure that
really should be able to be scaled up considerably higher than what
we've been able to do in the past. When we were stuck with
inheritance + constraint exclusion, we could say "well, there's not
really any point because you'll hit these other limits anyway". But I
think now that's not really true. This patch eliminates one of the
core scalability problems in this area, and provides infrastructure
for attacking some of the others. I hope that people will step up and
do that. There's a huge opportunity here for PostgreSQL to become
relevant in use cases where it currently falters badly, and we should
try to take advantage of it. This patch is a big step by itself, but
if we ignore the potential to do more with this as the base we will be
leaving a lot of "win" on the table.

Agreed on all counts.

Thanks,
Amit

[1] https://en.wikipedia.org/wiki/Multiple_granularity_locking

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

#190Maksim Milyutin
m.milyutin@postgrespro.ru
In reply to: Robert Haas (#177)
Re: Declarative partitioning - another take

Hi, everyone!

08.12.16 18:25, Robert Haas wrote:

Of course, this is the beginning, not the end. I've been thinking
about next steps -- here's an expanded list:

- more efficient plan-time partition pruning (constraint exclusion is too slow)
- run-time partition pruning
- partition-wise join (Ashutosh Bapat is already working on this)
- try to reduce lock levels
- hash partitioning
- the ability to create an index on the parent and have all of the
children inherit it; this should work something like constraint
inheritance. you could argue that this doesn't add any real new
capability but it's a huge usability feature.
- teaching autovacuum enough about inheritance hierarchies for it to
update the parent statistics when they get stale despite the lack of
any actual inserts/updates/deletes to the parent. this has been
pending for a long time, but it's only going to get more important
- row movement (aka avoiding the need for an ON UPDATE trigger on each
partition)
- insert (and eventually update) tuple routing for foreign partitions
- not scanning the parent
- fixing the insert routing so that we can skip tuple conversion where possible
- fleshing out the documentation

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the
children inherit it;

The first one has been implemented in pg_pathman somehow, but the code
relies on dirty hacks, so the FDW API has to be improved. As for the
extended index support, it doesn't look like a super-hard task.

--
Maksim Milyutin
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

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

#191Amit Langote
amitlangote09@gmail.com
In reply to: Venkata B Nagothi (#189)
Re: Declarative partitioning - another take

On Fri, Dec 9, 2016 at 3:16 PM, Venkata B Nagothi <nag1010@gmail.com> wrote:

Hi,

I am testing the partitioning feature from the latest master and got the
following error while loading the data -

db01=# create table orders_y1993 PARTITION OF orders FOR VALUES FROM
('1993-01-01') TO ('1993-12-31');
CREATE TABLE

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0 of
8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"

Hmm. Could you tell what relation the file/relfilenode 16412 belongs to?

Also, is orders_y1993 the only partition of orders? How about \d+ orders?

Thanks,
Amit

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

#192Venkata B Nagothi
nag1010@gmail.com
In reply to: Amit Langote (#191)
Re: Declarative partitioning - another take

Regards,

Venkata B N
Database Consultant

On Fri, Dec 9, 2016 at 11:11 PM, Amit Langote <amitlangote09@gmail.com>
wrote:

On Fri, Dec 9, 2016 at 3:16 PM, Venkata B Nagothi <nag1010@gmail.com>
wrote:

Hi,

I am testing the partitioning feature from the latest master and got the
following error while loading the data -

db01=# create table orders_y1993 PARTITION OF orders FOR VALUES FROM
('1993-01-01') TO ('1993-12-31');
CREATE TABLE

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only

0 of

8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM

|Clerk#000001993|0|ithely

regular pack"

Hmm. Could you tell what relation the file/relfilenode 16412 belongs to?

db01=# select relname from pg_class where relfilenode=16412 ;
relname
--------------
orders_y1997
(1 row)

I VACUUMED the partition and then re-ran the copy command and no luck.

db01=# vacuum orders_y1997;
VACUUM

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"

I do not quite understand the below behaviour as well. I VACUUMED 1997
partition and then i got an error for 1992 partition and then after 1996
and then after 1994 and so on.

postgres=# \c db01
You are now connected to database "db01" as user "dba".
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"
db01=# vacuum orders_y1997;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 3942 in file "base/16384/16406": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 75445:
"1993510|185287|F|42667.9|1992-08-15|2-HIGH |Clerk#000000079|0|
dugouts above the even "
db01=# select relname from pg_class where relfilenode=16406;
relname
--------------
orders_y1992
(1 row)

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 3942 in file "base/16384/16406": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 75396:
"1993317|260510|F|165852|1992-12-13|5-LOW
|Clerk#000003023|0|regular foxes. ironic dependenc..."
db01=# vacuum orders_y1992;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 3708 in file "base/16384/16394": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 178820:
"4713957|286270|O|200492|1996-10-01|1-URGENT
|Clerk#000001993|0|uriously final packages. slyly "
db01=# select relname from pg_class where relfilenode=16394;
relname
--------------
orders_y1996
(1 row)

db01=# vacuum orders_y1996;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 5602 in file "base/16384/16403": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 147390:
"3882662|738010|F|199365|1994-12-26|5-LOW |Clerk#000001305|0|ar
instructions above the expre..."
db01=# select relname from pg_class where relfilenode=16403;
relname
--------------
orders_y1994
(1 row)

db01=# vacuum orders_y1994;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 5561 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 59276:
"1572448|646948|O|25658.6|1997-05-02|4-NOT SPECIFIED|Clerk#000001993|0|es.
ironic, regular p"

*And finally the error again occurred for 1997 partition*

db01=# select relname from pg_class where relfilenode=16412;
relname
--------------
orders_y1997
(1 row)

db01=# vacuum orders_y1997;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"
db01=#

Am i not understanding anything here ?

Also, is orders_y1993 the only partition of orders? How about \d+ orders?

Yes, i created multiple yearly partitions for orders table. I wanted to
1993 year's data first and see if the data goes into orders_y1993 partition
and itseems that, the CSV contains 1997 data as wellCopy command found a

db01=# \d+ orders
Table "public.orders"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
-----------------+-----------------------+-----------+----------+---------+----------+--------------+-------------
o_orderkey | integer | | | |
plain | |
o_custkey | integer | | | |
plain | |
o_orderstatus | character(1) | | | |
extended | |
o_totalprice | real | | | |
plain | |
o_orderdate | date | | not null | |
plain | |
o_orderpriority | character(15) | | | |
extended | |
o_clerk | character(15) | | | |
extended | |
o_shippriority | integer | | | |
plain | |
o_comment | character varying(79) | | | |
extended | |
Partition key: RANGE (o_orderdate)
Partitions: orders_y1992 FOR VALUES FROM ('1992-01-01') TO ('1992-12-31'),
orders_y1993 FOR VALUES FROM ('1993-01-01') TO ('1993-12-31'),
orders_y1994 FOR VALUES FROM ('1994-01-01') TO ('1994-12-31'),
orders_y1995 FOR VALUES FROM ('1995-01-01') TO ('1995-12-31'),
orders_y1996 FOR VALUES FROM ('1996-01-01') TO ('1996-12-31'),
orders_y1997 FOR VALUES FROM ('1997-01-01') TO ('1997-12-31'),
orders_y1998 FOR VALUES FROM ('1998-01-01') TO ('1998-12-31')

#193Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Robert Haas (#170)
Re: Declarative partitioning - another take

Hi,

On 12/07/2016 07:20 PM, Robert Haas wrote:

On Wed, Dec 7, 2016 at 11:53 AM, Erik Rijkers <er@xs4all.nl> wrote:

My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.

Yes, fixed here too. Thanks.

Thanks for the report - that was a good catch.

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

I've been working on a review / testing of the partitioning patch, but
have been unable to submit it before the commit due to a lot of travel.
However, at least some of the points seem to be still valid, so let me
share it as an after-commit review. Most of the issues are fairly minor
(possibly even nitpicking).

review
------

1) src/include/catalog/pg_partitioned_table.h contains this bit:

* $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $

2) I'm wondering whether having 'table' in the catalog name (and also in
the new relkind) is too limiting. I assume we'll have partitioned
indexes one day, for example - do we expect to use the same catalogs?

3) A comment within BeginCopy (in copy.c) says:

* Initialize state for CopyFrom tuple routing. Watch out for
* any foreign partitions.

But the code does not seem to be doing that (at least I don't see any
obvious checks for foreign partitions). Also, the comment should
probably at least mention why foreign partitions need extra care.

To nitpick, the 'pd' variable in that block seems rather pointless - we
can assign directly to cstate->partition_dispatch_info.

4) I see GetIndexOpClass() got renamed to ResolveOpClass(). I find the
new name rather strange - all other similar functions start with "Get",
so I guess "GetOpClass()" would be better. But I wonder if the rename
was even necessary, considering that it still deals with index operator
classes (although now also in context of partition keys). If the rename
really is needed, isn't that a sign that the function does not belong to
indexcmds.c anymore?

5) Half the error messages use 'child table' while the other half uses
'partition'. I think we should be using 'partition' as child tables
really have little meaning outside inheritance (which is kinda hidden
behind the new partitioning stuff).

6) The psql tab-completion seems a bit broken, because it only offers
the partitions, not the parent table. Which is usually exactly the
opposite of what the user wants.

testing
-------

I've also done quite a bit of testing with different partition layouts
(single-level list/range partitioning, multi-level partitioning etc.),
with fairly large number (~10k) of partitions. The scripts I used are
available here: https://bitbucket.org/tvondra/partitioning-tests

1) There seems to be an issue when a partition is created and then
accessed within the same transaction, i.e. for example

BEGIN;
... create parent ...
... create partition ....
... insert into parent ...
COMMIT;

which simply fails with an error like this:

ERROR: no partition of relation "range_test_single" found for row
DETAIL: Failing row contains (99000, 99000).

Of course, the partition is there. And interestingly enough, this works
perfectly fine when executed without the explicit transaction, so I
assume it's some sort of cache invalidation mix-up.

2) When doing a SELECT COUNT(*) from the partitioned table, I get a plan
like this:

QUERY PLAN
-------------------------------------------------------------------
Finalize Aggregate (cost=124523.64..124523.65 rows=1 width=8)
-> Gather (cost=124523.53..124523.64 rows=1 width=8)
Workers Planned: 1
-> Partial Aggregate (cost=123523.53..123523.54 rows=1 ...)
-> Append (cost=0.00..108823.53 rows=5880001 width=0)
-> Parallel Seq Scan on parent ...
-> Parallel Seq Scan on partition_1 ...
-> Parallel Seq Scan on partition_2 ...
-> Parallel Seq Scan on partition_3 ...
-> Parallel Seq Scan on partition_4 ...
-> Parallel Seq Scan on partition_5 ...
-> Parallel Seq Scan on partition_6 ...
... and the rest of the 10k partitions

So if I understand the plan correctly, we first do a parallel scan of
the parent, then partition_1, partition_2 etc. But AFAIK we scan the
tables in Append sequentially, and each partition only has 1000 rows
each, making the parallel execution rather inefficient. Unless we scan
the partitions concurrently.

In any case, as this happens even with plain inheritance, it's probably
more about the parallel query than about the new partitioning patch.

3) The last issue I noticed is that while

EXPLAIN SELECT * FROM partitioned_table WHERE id = 1292323;

works just fine (it takes fair amount of time to plan with 10k
partitions, but that's expected), this

EXPLAIN UPDATE partitioned_table SET x = 1 WHERE id = 1292323;

allocates a lot of memory (~8GB on my laptop, before it gets killed by
OOM killer). Again, the same thing happens with plain inheritance-based
partitioning, so it's probably not a bug in the partitioning patch.

I'm mentioning it here because I think the new partitioning will
hopefully get more efficient and handle large partition counts more
efficiently (the inheritance only really works for ~100 partitions,
which is probably why no one complained about OOM during UPDATEs). Of
course, 10k partitions is a bit extreme (good for testing, though).

regards

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

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

#194Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Venkata B Nagothi (#192)
Re: Declarative partitioning - another take

Hi,

On 2016/12/11 10:02, Venkata B Nagothi wrote:

On Fri, Dec 9, 2016 at 11:11 PM, Amit Langote <amitlangote09@gmail.com>
wrote:

On Fri, Dec 9, 2016 at 3:16 PM, Venkata B Nagothi <nag1010@gmail.com>
wrote:

I am testing the partitioning feature from the latest master and got the
following error while loading the data -

db01=# create table orders_y1993 PARTITION OF orders FOR VALUES FROM
('1993-01-01') TO ('1993-12-31');
CREATE TABLE

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only

0 of

8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM

|Clerk#000001993|0|ithely

regular pack"

Hmm. Could you tell what relation the file/relfilenode 16412 belongs to?

db01=# select relname from pg_class where relfilenode=16412 ;
relname
--------------
orders_y1997
(1 row)

I VACUUMED the partition and then re-ran the copy command and no luck.

db01=# vacuum orders_y1997;
VACUUM

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"

I do not quite understand the below behaviour as well. I VACUUMED 1997
partition and then i got an error for 1992 partition and then after 1996
and then after 1994 and so on.

[ ... ]

db01=# vacuum orders_y1997;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"
db01=#

Am i not understanding anything here ?

I could not reproduce this issue. Also, I could not say what might have
gone wrong based only on the information I have seen so far.

Have you tried inserting the same data using insert?

create table orders_unpartitioned (like orders);
copy orders_unpartitioned from '/data/orders-1993.csv';
insert into orders select * from orders_unpartitioned;

Thanks,
Amit

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

#195Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Tomas Vondra (#193)
1 attachment(s)
Re: Declarative partitioning - another take

Hi Tomas,

On 2016/12/12 10:02, Tomas Vondra wrote:

On 12/07/2016 07:20 PM, Robert Haas wrote:

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

I've been working on a review / testing of the partitioning patch, but
have been unable to submit it before the commit due to a lot of travel.
However, at least some of the points seem to be still valid, so let me
share it as an after-commit review. Most of the issues are fairly minor
(possibly even nitpicking).

Thanks a lot for the review comments and testing!

I attach a patch that implements some of the fixes that you suggest below.
Also, I am merging a patch I sent earlier [1]/messages/by-id/c9737515-36ba-6a42-f788-1b8867e2dd38@lab.ntt.co.jp to avoid having to look at
multiple patches.

review
------

1) src/include/catalog/pg_partitioned_table.h contains this bit:

* $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $

Fixed.

2) I'm wondering whether having 'table' in the catalog name (and also in
the new relkind) is too limiting. I assume we'll have partitioned indexes
one day, for example - do we expect to use the same catalogs?

I am not sure I understand your idea of partitioned indexes, but I doubt
it would require entries in the catalog under consideration. Could you
perhaps elaborate more?

3) A comment within BeginCopy (in copy.c) says:

* Initialize state for CopyFrom tuple routing. Watch out for
* any foreign partitions.

But the code does not seem to be doing that (at least I don't see any
obvious checks for foreign partitions). Also, the comment should probably
at least mention why foreign partitions need extra care.

It's a stale comment after I took out the code that handled foreign
partitions in final versions of the tuple-routing patch. You may have
noticed in the commit message that tuple-routing does not work for foreign
partitions, because the changes I'd proposed weren't that good. Comment
fixed anyway.

To nitpick, the 'pd' variable in that block seems rather pointless - we
can assign directly to cstate->partition_dispatch_info.

OK, fixed. Also, fixed similar code in ExecInitModifyTable().

4) I see GetIndexOpClass() got renamed to ResolveOpClass(). I find the new
name rather strange - all other similar functions start with "Get", so I
guess "GetOpClass()" would be better. But I wonder if the rename was even
necessary, considering that it still deals with index operator classes
(although now also in context of partition keys). If the rename really is
needed, isn't that a sign that the function does not belong to indexcmds.c
anymore?

The fact that both index keys and partition keys have very similar
specification syntax means there would be some common code handling the
same, of which this is one (maybe, only) example. I didn't see much point
in finding a new place for the function, although I can see why it may be
a bit confusing to future readers of the code.

About the new name - seeing the top line in the old header comment, what
the function does is resolve a possibly explicitly specified operator
class name to an operator class OID. I hadn't changed the name in my
original patch, but Robert suggested the rename, which I thought made sense.

5) Half the error messages use 'child table' while the other half uses
'partition'. I think we should be using 'partition' as child tables really
have little meaning outside inheritance (which is kinda hidden behind the
new partitioning stuff).

One way to go about it may be to check all sites that can possibly report
an error involving child tables (aka "partitions") whether they know from
the context which name to use. I think it's possible, because we have
access to the parent relation in all such sites and looking at the relkind
can tell whether to call child tables "partitions".

6) The psql tab-completion seems a bit broken, because it only offers the
partitions, not the parent table. Which is usually exactly the opposite of
what the user wants.

Actually, I have not implemented any support for tab-completion for the
new DDL. I will work on that.

testing
-------

I've also done quite a bit of testing with different partition layouts
(single-level list/range partitioning, multi-level partitioning etc.),
with fairly large number (~10k) of partitions. The scripts I used are
available here: https://bitbucket.org/tvondra/partitioning-tests

1) There seems to be an issue when a partition is created and then
accessed within the same transaction, i.e. for example

BEGIN;
... create parent ...
... create partition ....
... insert into parent ...
COMMIT;

which simply fails with an error like this:

ERROR: no partition of relation "range_test_single" found for row
DETAIL: Failing row contains (99000, 99000).

Of course, the partition is there. And interestingly enough, this works
perfectly fine when executed without the explicit transaction, so I assume
it's some sort of cache invalidation mix-up.

That's a clearly bug. A relcache invalidation on the parent was missing
in create table ... partition of code path. Fixed.

2) When doing a SELECT COUNT(*) from the partitioned table, I get a plan
like this:

QUERY PLAN
-------------------------------------------------------------------
Finalize Aggregate (cost=124523.64..124523.65 rows=1 width=8)
-> Gather (cost=124523.53..124523.64 rows=1 width=8)
Workers Planned: 1
-> Partial Aggregate (cost=123523.53..123523.54 rows=1 ...)
-> Append (cost=0.00..108823.53 rows=5880001 width=0)
-> Parallel Seq Scan on parent ...
-> Parallel Seq Scan on partition_1 ...
-> Parallel Seq Scan on partition_2 ...
-> Parallel Seq Scan on partition_3 ...
-> Parallel Seq Scan on partition_4 ...
-> Parallel Seq Scan on partition_5 ...
-> Parallel Seq Scan on partition_6 ...
... and the rest of the 10k partitions

So if I understand the plan correctly, we first do a parallel scan of the
parent, then partition_1, partition_2 etc. But AFAIK we scan the tables in
Append sequentially, and each partition only has 1000 rows each, making
the parallel execution rather inefficient. Unless we scan the partitions
concurrently.

In any case, as this happens even with plain inheritance, it's probably
more about the parallel query than about the new partitioning patch.

Yes, I have seen some discussion [2]/messages/by-id/83755E07-C435-493F-9F93-F727604D66A1@gmail.com about a Parallel Append, which would
result in plans you're probably thinking of.

3) The last issue I noticed is that while

EXPLAIN SELECT * FROM partitioned_table WHERE id = 1292323;

works just fine (it takes fair amount of time to plan with 10k partitions,
but that's expected), this

EXPLAIN UPDATE partitioned_table SET x = 1 WHERE id = 1292323;

allocates a lot of memory (~8GB on my laptop, before it gets killed by OOM
killer). Again, the same thing happens with plain inheritance-based
partitioning, so it's probably not a bug in the partitioning patch.

I'm mentioning it here because I think the new partitioning will hopefully
get more efficient and handle large partition counts more efficiently (the
inheritance only really works for ~100 partitions, which is probably why
no one complained about OOM during UPDATEs). Of course, 10k partitions is
a bit extreme (good for testing, though).

Plans with the inheritance parents as target tables (update/delete) go
through inheritance_planner() in the optimizer, which currently has a
design that is based on certain assumptions about traditional inheritance.
It's possible hopefully to make it less expensive for the partitioned tables.

Thanks,
Amit

[1]: /messages/by-id/c9737515-36ba-6a42-f788-1b8867e2dd38@lab.ntt.co.jp
/messages/by-id/c9737515-36ba-6a42-f788-1b8867e2dd38@lab.ntt.co.jp

[2]: /messages/by-id/83755E07-C435-493F-9F93-F727604D66A1@gmail.com
/messages/by-id/83755E07-C435-493F-9F93-F727604D66A1@gmail.com

Attachments:

misc-code-fixes-1.patchtext/x-diff; name=misc-code-fixes-1.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ffea74855..c09c9f28a7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1887,6 +1887,10 @@ heap_drop_with_catalog(Oid relid)
 
 	if (parent)
 	{
+		/*
+		 * Invalidate the parent's relcache so that the partition is no longer
+		 * included in its partition descriptor.
+		 */
 		CacheInvalidateRelcache(parent);
 		heap_close(parent, NoLock);		/* keep the lock */
 	}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 219d380cde..cc09fb3e55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1492,7 +1492,7 @@ generate_partition_qual(Relation rel, bool recurse)
  *			Construct values[] and isnull[] arrays for the partition key
  *			of a tuple.
  *
- *	pkinfo			partition key execution info
+ *	pd				Partition dispatch object of the partitioned table
  *	slot			Heap tuple from which to extract partition key
  *	estate			executor state for evaluating any partition key
  *					expressions (must be non-NULL)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 270be0af18..e9177491e2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1403,31 +1403,28 @@ BeginCopy(ParseState *pstate,
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
 
-		/*
-		 * Initialize state for CopyFrom tuple routing.  Watch out for
-		 * any foreign partitions.
-		 */
+		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			PartitionDispatch *pd;
 			List		   *leaf_parts;
 			ListCell	   *cell;
 			int				i,
-							num_parted,
-							num_leaf_parts;
+							num_parted;
 			ResultRelInfo  *leaf_part_rri;
 
 			/* Get the tuple-routing information and lock partitions */
-			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-												  &num_parted, &leaf_parts);
-			num_leaf_parts = list_length(leaf_parts);
-			cstate->partition_dispatch_info = pd;
+			cstate->partition_dispatch_info =
+					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+													 &num_parted,
+													 &leaf_parts);
 			cstate->num_dispatch = num_parted;
-			cstate->num_partitions = num_leaf_parts;
-			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
-														sizeof(ResultRelInfo));
+			cstate->num_partitions = list_length(leaf_parts);
+			cstate->partitions = (ResultRelInfo *)
+									palloc(cstate->num_partitions *
+													sizeof(ResultRelInfo));
 			cstate->partition_tupconv_maps = (TupleConversionMap **)
-						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+									palloc0(cstate->num_partitions *
+											sizeof(TupleConversionMap *));
 
 			leaf_part_rri = cstate->partitions;
 			i = 0;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c77b216d4f..e0e323cc7f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -777,12 +777,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * it does not return on error.
 		 */
 		check_new_partition_bound(relname, parent, bound);
-		heap_close(parent, NoLock);
 
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, bound);
 
 		/*
+		 * We must invalidate the parent's relcache so that the next
+		 * CommandCounterIncrement() will cause the same to be rebuilt with
+		 * the new partition's info included in its partition descriptor.
+		 */
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);
+
+		/*
 		 * The code that follows may also update the pg_class tuple to update
 		 * relnumchecks, so bump up the command counter to avoid the "already
 		 * updated by self" error.
@@ -1614,8 +1621,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * In case of a partition, there are no new column definitions, only
-	 * dummy ColumnDefs created for column constraints.  We merge these
-	 * constraints inherited from the parent.
+	 * dummy ColumnDefs created for column constraints.  We merge them
+	 * with the constraints inherited from the parent.
 	 */
 	if (is_partition)
 	{
@@ -2030,8 +2037,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
-				 * Partitions have only one parent, so conflict should never
-				 * occur
+				 * Partitions have only one parent and have no column
+				 * definitions of their own, so conflict should never occur.
 				 */
 				Assert(!is_partition);
 
@@ -2118,8 +2125,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * Now that we have the column definition list for a partition, we can
-	 * check whether the columns referenced in column option specifications
-	 * actually exist.  Also, we merge the options into the corresponding
+	 * check whether the columns referenced in the column constraint specs
+	 * actually exist.  Also, we merge the constraints into the corresponding
 	 * column definitions.
 	 */
 	if (is_partition && list_length(saved_schema) > 0)
@@ -13351,8 +13358,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	}
 
 	/*
-	 * Invalidate the relcache so that the new partition is now included
-	 * in rel's partition descriptor.
+	 * Invalidate the parent's relcache so that the new partition is now
+	 * included its partition descriptor.
 	 */
 	CacheInvalidateRelcache(rel);
 
@@ -13414,8 +13421,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
-	 * Invalidate the relcache so that the partition is no longer included
-	 * in our partition descriptor.
+	 * Invalidate the parent's relcache so that the partition is no longer
+	 * included in its partition descriptor.
 	 */
 	CacheInvalidateRelcache(rel);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c0b58d1841..578e324d73 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1721,23 +1721,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		PartitionDispatch  *pd;
 		int					i,
 							j,
-							num_parted,
-							num_leaf_parts;
+							num_parted;
 		List			   *leaf_parts;
 		ListCell		   *cell;
 		ResultRelInfo	   *leaf_part_rri;
 
-		/* Form the partition node tree and lock partitions */
-		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-											  &num_parted, &leaf_parts);
-		mtstate->mt_partition_dispatch_info = pd;
+		/* Get the tuple-routing information and lock partitions */
+		mtstate->mt_partition_dispatch_info =
+					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+													 &num_parted,
+													 &leaf_parts);
 		mtstate->mt_num_dispatch = num_parted;
-		num_leaf_parts = list_length(leaf_parts);
-		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_num_partitions = list_length(leaf_parts);
 		mtstate->mt_partitions = (ResultRelInfo *)
-						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+						palloc0(mtstate->mt_num_partitions *
+												sizeof(ResultRelInfo));
 		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
-					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+						palloc0(mtstate->mt_num_partitions *
+												sizeof(TupleConversionMap *));
 
 		leaf_part_rri = mtstate->mt_partitions;
 		i = j = 0;
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index cec54ae62e..2c6b6cf3a0 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -7,7 +7,7 @@
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ * src/include/catalog/pg_partitioned_table.h
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -47,7 +47,7 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 #ifdef CATALOG_VARLEN
 	oidvector		partclass;		/* operator class to compare keys */
 	oidvector		partcollation;	/* user-specified collation for keys */
-	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+	pg_node_tree	partexprs;		/* list of expressions in the partition
 									 * key; one item for each zero entry in
 									 * partattrs[] */
 #endif
#196Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#170)
Re: Declarative partitioning - another take

On 12/7/16 1:20 PM, Robert Haas wrote:

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

This page
<https://www.postgresql.org/docs/devel/static/ddl-partitioning.html&gt;,
which is found via the index entry for "Partitioning", should be updated
for the new functionality.

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

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

#197Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Langote (#195)
Re: Declarative partitioning - another take

On 12/12/2016 07:37 AM, Amit Langote wrote:

Hi Tomas,

On 2016/12/12 10:02, Tomas Vondra wrote:

2) I'm wondering whether having 'table' in the catalog name (and also in
the new relkind) is too limiting. I assume we'll have partitioned indexes
one day, for example - do we expect to use the same catalogs?

I am not sure I understand your idea of partitioned indexes, but I doubt
it would require entries in the catalog under consideration. Could you
perhaps elaborate more?

OK, let me elaborate. Let's say we have a partitioned table, and I want
to create an index. The index may be either "global" i.e. creating a
single relation for data from all the partitions, or "local" (i.e.
partitioned the same way as the table).

Local indexes are easier to implement (it's essentially what we have
now, except that we need to create the indexes manually for each
partition), and don't work particularly well for some use cases (e.g.
unique constraints). This is what I mean by "partitioned indexes".

If the index is partitioned just like the table, we probably won't need
to copy the partition key info (so, nothing in pg_partitioned_table).
I'm not sure it makes sense to partition the index differently than the
table - I don't see a case where that would be useful.

The global indexes would work better for the unique constraint use case,
but it clearly contradicts our idea of TID (no information about which
partition that references).

So maybe the catalog really only needs to track info about tables? Not
sure. I'm just saying it'd be unfortunate to have _table in the name,
and end up using it for indexes too.

4) I see GetIndexOpClass() got renamed to ResolveOpClass(). I find the new
name rather strange - all other similar functions start with "Get", so I
guess "GetOpClass()" would be better. But I wonder if the rename was even
necessary, considering that it still deals with index operator classes
(although now also in context of partition keys). If the rename really is
needed, isn't that a sign that the function does not belong to indexcmds.c
anymore?

The fact that both index keys and partition keys have very similar
specification syntax means there would be some common code handling the
same, of which this is one (maybe, only) example. I didn't see much point
in finding a new place for the function, although I can see why it may be
a bit confusing to future readers of the code.

About the new name - seeing the top line in the old header comment, what
the function does is resolve a possibly explicitly specified operator
class name to an operator class OID. I hadn't changed the name in my
original patch, but Robert suggested the rename, which I thought made sense.

OK, I don't particularly like the name but I can live with that.

5) Half the error messages use 'child table' while the other half uses
'partition'. I think we should be using 'partition' as child tables really
have little meaning outside inheritance (which is kinda hidden behind the
new partitioning stuff).

One way to go about it may be to check all sites that can possibly report
an error involving child tables (aka "partitions") whether they know from
the context which name to use. I think it's possible, because we have
access to the parent relation in all such sites and looking at the relkind
can tell whether to call child tables "partitions".

Clearly, this is a consequence of building the partitioning on top of
inheritance (not objecting to that approach, merely stating a fact).

I'm fine with whatever makes the error messages more consistent, if it
does not make the code significantly more complex. It's a bit confusing
when some use 'child tables' and others 'partitions'. I suspect even a
single DML command may return a mix of those, depending on where exactly
it fails (old vs. new code).

6) The psql tab-completion seems a bit broken, because it only offers the
partitions, not the parent table. Which is usually exactly the opposite of
what the user wants.

Actually, I have not implemented any support for tab-completion for the
new DDL. I will work on that.

Aha! So the problem probably is that psql does not recognize the new
'partitioned table' relkind, and only looks at regular tables.

testing
-------

2) When doing a SELECT COUNT(*) from the partitioned table, I get a plan
like this:

QUERY PLAN
-------------------------------------------------------------------
Finalize Aggregate (cost=124523.64..124523.65 rows=1 width=8)
-> Gather (cost=124523.53..124523.64 rows=1 width=8)
Workers Planned: 1
-> Partial Aggregate (cost=123523.53..123523.54 rows=1 ...)
-> Append (cost=0.00..108823.53 rows=5880001 width=0)
-> Parallel Seq Scan on parent ...
-> Parallel Seq Scan on partition_1 ...
-> Parallel Seq Scan on partition_2 ...
-> Parallel Seq Scan on partition_3 ...
-> Parallel Seq Scan on partition_4 ...
-> Parallel Seq Scan on partition_5 ...
-> Parallel Seq Scan on partition_6 ...
... and the rest of the 10k partitions

So if I understand the plan correctly, we first do a parallel scan of the
parent, then partition_1, partition_2 etc. But AFAIK we scan the tables in
Append sequentially, and each partition only has 1000 rows each, making
the parallel execution rather inefficient. Unless we scan the partitions
concurrently.

In any case, as this happens even with plain inheritance, it's probably
more about the parallel query than about the new partitioning patch.

Yes, I have seen some discussion [2] about a Parallel Append, which would
result in plans you're probably thinking of.

Actually, I think that thread is about allowing partial paths even if
only some appendrel members support it. My point is that in this case
it's a bit silly to even build the partial paths, when the partitions
only have 1000 rows each.

I kinda suspect we only look at the total appendrel rowcount estimate,
but haven't checked.

3) The last issue I noticed is that while

EXPLAIN SELECT * FROM partitioned_table WHERE id = 1292323;

works just fine (it takes fair amount of time to plan with 10k partitions,
but that's expected), this

EXPLAIN UPDATE partitioned_table SET x = 1 WHERE id = 1292323;

allocates a lot of memory (~8GB on my laptop, before it gets killed by OOM
killer). Again, the same thing happens with plain inheritance-based
partitioning, so it's probably not a bug in the partitioning patch.

I'm mentioning it here because I think the new partitioning will hopefully
get more efficient and handle large partition counts more efficiently (the
inheritance only really works for ~100 partitions, which is probably why
no one complained about OOM during UPDATEs). Of course, 10k partitions is
a bit extreme (good for testing, though).

Plans with the inheritance parents as target tables (update/delete)
go through inheritance_planner() in the optimizer, which currently
has a design that is based on certain assumptions about traditional
inheritance. It's possible hopefully to make it less expensive for
the partitioned tables.

Yes, I know. I wasn't really reporting it as a bug in the partitioning
patch, but more as a rather surprising difference between plain SELECT
and UPDATE.

Am I right that one of the ambitions of the new partitioning is to
improve behavior with large number of partitions?

At first I thought it's somewhat related to the FDW sharding (each node
being a partition and having local subpartitions), but I realize the
planner will only deal with the node partitions I guess.

regards

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

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

#198Dmitry Ivanov
d.ivanov@postgrespro.ru
In reply to: Robert Haas (#183)
1 attachment(s)
Re: Declarative partitioning - another take

Hi guys,

Looks like I've just encountered a bug. Please excuse me for the messy
email, I don't have much time at the moment.

Here's the test case:

create table test(val int) partition by range (val);
create table test_1 partition of test for values from (1) to (1000)
partition by range(val);
create table test_2 partition of test for values from (1000) to (2000)
partition by range(val);
create table test_1_1 partition of test_1 for values from (1) to (500)
partition by range(val);
create table test_1_2 partition of test_1 for values from (500) to (1000)
partition by range(val);
create table test_1_1_1 partition of test_1_1 for values from (1) to (500);
create table test_1_2_1 partition of test_1_2 for values from (500) to
(1000);

/* insert a row into "test_1_2_1" */
insert into test values(600);

/* what we EXPECT to see */
select *, tableoid::regclass from test;
val | tableoid
-----+------------
600 | test_1_2_1
(1 row)

/* what we ACTUALLY see */
insert into test values(600);
ERROR: no partition of relation "test_1_1" found for row
DETAIL: Failing row contains (600).

How does this happen? This is how "PartitionDispatch" array looks like:

test | test_1 | test_2 | test_1_1 | test_1_2

which means that this code (partition.c : 1025):

/*
* We can assign indexes this way because of the way
* parted_rels has been generated.
*/
pd[i]->indexes[j] = -(i + 1 + m);

doesn't work, since partitions are not always placed right after the parent
(implied by index "m").

We have to take into account the total amount of partitions we've
encountered so far (right before index "i").

I've attached a patch with a hotfix, but the code looks so-so and has a
smell. I think it must be rewritten. This bug hunt surely took a while: I
had to recheck all of the steps several times.

--
Dmitry Ivanov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

Attachments:

hacky_partitioning_hotfix.difftext/x-patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 219d380..119a41d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -950,7 +950,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			   *parted_rels;
 	ListCell   *lc;
 	int			i,
-				k;
+				k,
+				children_so_far = 0;
 
 	/*
 	 * Lock partitions and make a list of the partitioned ones to prepare
@@ -1026,11 +1027,18 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 				 * We can assign indexes this way because of the way
 				 * parted_rels has been generated.
 				 */
-				pd[i]->indexes[j] = -(i + 1 + m);
+				pd[i]->indexes[j] = -(1 + m + children_so_far);
 				m++;
 			}
 		}
 		i++;
+
+		/*
+		 * Children of this parent are placed
+		 * after all children of all previous parents,
+		 * so we have to take this into account.
+		 */
+		children_so_far += partdesc->nparts;
 	}
 
 	return pd;
#199Dmitry Ivanov
d.ivanov@postgrespro.ru
In reply to: Dmitry Ivanov (#198)
1 attachment(s)
Re: Declarative partitioning - another take

Huh, this code is broken as well. We have to ignore partitions that don't
have any subpartitions. Patch is attached below (v2).

--
Dmitry Ivanov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

Attachments:

hacky_partitioning_hotfix_v2.difftext/x-patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 219d380..6555c7c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -950,7 +950,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			   *parted_rels;
 	ListCell   *lc;
 	int			i,
-				k;
+				k,
+				partitioned_children_so_far = 0;
 
 	/*
 	 * Lock partitions and make a list of the partitioned ones to prepare
@@ -1001,7 +1002,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		PartitionKey partkey = RelationGetPartitionKey(partrel);
 		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
 		int			j,
-					m;
+					my_partitioned_children;
 
 		pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
 		pd[i]->reldesc = partrel;
@@ -1010,7 +1011,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		pd[i]->partdesc = partdesc;
 		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
 
-		m = 0;
+		my_partitioned_children = 0;
 		for (j = 0; j < partdesc->nparts; j++)
 		{
 			Oid			partrelid = partdesc->oids[j];
@@ -1026,11 +1027,21 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 				 * We can assign indexes this way because of the way
 				 * parted_rels has been generated.
 				 */
-				pd[i]->indexes[j] = -(i + 1 + m);
-				m++;
+				pd[i]->indexes[j] = -(1 +
+									  my_partitioned_children +
+									  partitioned_children_so_far);
+
+				my_partitioned_children++;
 			}
 		}
 		i++;
+
+		/*
+		 * Children of this parent should be placed after all
+		 * partitioned children of all previous parents, so we
+		 * have to take this into account.
+		 */
+		partitioned_children_so_far += my_partitioned_children;
 	}
 
 	return pd;
#200Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Peter Eisentraut (#196)
Re: Declarative partitioning - another take

On 2016/12/12 23:14, Peter Eisentraut wrote:

On 12/7/16 1:20 PM, Robert Haas wrote:

I've committed 0001 - 0006 with that correction and a few other
adjustments. There's plenty of room for improvement here, and almost
certainly some straight-up bugs too, but I think we're at a point
where it will be easier and less error-prone to commit follow on
changes incrementally rather than by continuously re-reviewing a very
large patch set for increasingly smaller changes.

This page
<https://www.postgresql.org/docs/devel/static/ddl-partitioning.html&gt;,
which is found via the index entry for "Partitioning", should be updated
for the new functionality.

A patch was included to do that, but it didn't do more than replacing the
old DDL listing by the new partitioning commands. As Robert said in the
email you quoted, that's not enough. So I'm trying to come up with a
patch updating the page explaining the new functionality better.

Thanks,
Amit

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

#201Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Dmitry Ivanov (#199)
5 attachment(s)
Re: Declarative partitioning - another take

Hi,

On 2016/12/13 2:45, Dmitry Ivanov wrote:

Huh, this code is broken as well. We have to ignore partitions that don't
have any subpartitions. Patch is attached below (v2).

Good catch and thanks a lot for the patch! I have revised it a bit and
added some explanatory comments to that function.

Attaching the above patch, along with some other patches posted earlier,
and one more patch fixing another bug I found. Patch descriptions follow:

0001-Miscallaneous-code-and-comment-improvements.patch

Fixes some obsolete comments while improving others. Also, implements
some of Tomas Vondra's review comments.

0002-Miscallaneous-documentation-improvements.patch

Fixes inconsistencies and improves some examples in the documentation.
Also, mentions the limitation regarding row movement.

0003-Invalidate-the-parent-s-relcache-after-partition-cre.patch

Fixes a bug reported by Tomas, whereby a parent's relcache was not
invalidated after creation of a new partition using CREATE TABLE PARTITION
OF. This resulted in tuple-routing code not being to able to find a
partition that was created by the last command in a given transaction.

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patch

Fixes a bug I found this morning, whereby an internal partition's
constraint would not be enforced if it is targeted directly. See example
below:

create table p (a int, b char) partition by range (a);
create table p1 partition of p for values from (1) to (10) partition by
list (b);
create table p1a partition of p1 for values in ('a');
insert into p1 values (0, 'a'); -- should fail, but doesn't

0005-Fix-a-tuple-routing-bug-in-multi-level-partitioned-t.patch

Fixes a bug discovered by Dmitry Ivanov, whereby wrong indexes were
assigned to the partitions of lower levels (level > 1), causing spurious
"partition not found" error as demonstrated in his email [1]/messages/by-id/e6c56fe9-4b87-4f64-ac6f-bc99675f3f9e@postgrespro.ru.

Sorry about sending some of these patches repeatedly though.

Thanks,
Amit

[1]: /messages/by-id/e6c56fe9-4b87-4f64-ac6f-bc99675f3f9e@postgrespro.ru
/messages/by-id/e6c56fe9-4b87-4f64-ac6f-bc99675f3f9e@postgrespro.ru

Attachments:

0001-Miscallaneous-code-and-comment-improvements.patchtext/x-diff; name=0001-Miscallaneous-code-and-comment-improvements.patchDownload
From f089201eab5cb8989aad3403ad9a7b43d42c8fa7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:06:08 +0900
Subject: [PATCH 1/5] Miscallaneous code and comment improvements.

---
 src/backend/catalog/heap.c                 |  4 ++++
 src/backend/catalog/partition.c            |  2 +-
 src/backend/commands/copy.c                | 27 ++++++++++++---------------
 src/backend/commands/tablecmds.c           | 20 ++++++++++----------
 src/backend/executor/nodeModifyTable.c     | 22 +++++++++++-----------
 src/include/catalog/pg_partitioned_table.h |  4 ++--
 6 files changed, 40 insertions(+), 39 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ffea74855..c09c9f28a7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1887,6 +1887,10 @@ heap_drop_with_catalog(Oid relid)
 
 	if (parent)
 	{
+		/*
+		 * Invalidate the parent's relcache so that the partition is no longer
+		 * included in its partition descriptor.
+		 */
 		CacheInvalidateRelcache(parent);
 		heap_close(parent, NoLock);		/* keep the lock */
 	}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 219d380cde..cc09fb3e55 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1492,7 +1492,7 @@ generate_partition_qual(Relation rel, bool recurse)
  *			Construct values[] and isnull[] arrays for the partition key
  *			of a tuple.
  *
- *	pkinfo			partition key execution info
+ *	pd				Partition dispatch object of the partitioned table
  *	slot			Heap tuple from which to extract partition key
  *	estate			executor state for evaluating any partition key
  *					expressions (must be non-NULL)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 270be0af18..e9177491e2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1403,31 +1403,28 @@ BeginCopy(ParseState *pstate,
 					 errmsg("table \"%s\" does not have OIDs",
 							RelationGetRelationName(cstate->rel))));
 
-		/*
-		 * Initialize state for CopyFrom tuple routing.  Watch out for
-		 * any foreign partitions.
-		 */
+		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			PartitionDispatch *pd;
 			List		   *leaf_parts;
 			ListCell	   *cell;
 			int				i,
-							num_parted,
-							num_leaf_parts;
+							num_parted;
 			ResultRelInfo  *leaf_part_rri;
 
 			/* Get the tuple-routing information and lock partitions */
-			pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-												  &num_parted, &leaf_parts);
-			num_leaf_parts = list_length(leaf_parts);
-			cstate->partition_dispatch_info = pd;
+			cstate->partition_dispatch_info =
+					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+													 &num_parted,
+													 &leaf_parts);
 			cstate->num_dispatch = num_parted;
-			cstate->num_partitions = num_leaf_parts;
-			cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
-														sizeof(ResultRelInfo));
+			cstate->num_partitions = list_length(leaf_parts);
+			cstate->partitions = (ResultRelInfo *)
+									palloc(cstate->num_partitions *
+													sizeof(ResultRelInfo));
 			cstate->partition_tupconv_maps = (TupleConversionMap **)
-						palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+									palloc0(cstate->num_partitions *
+											sizeof(TupleConversionMap *));
 
 			leaf_part_rri = cstate->partitions;
 			i = 0;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5856e72918..a9650114d4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1614,8 +1614,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * In case of a partition, there are no new column definitions, only
-	 * dummy ColumnDefs created for column constraints.  We merge these
-	 * constraints inherited from the parent.
+	 * dummy ColumnDefs created for column constraints.  We merge them
+	 * with the constraints inherited from the parent.
 	 */
 	if (is_partition)
 	{
@@ -2030,8 +2030,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
-				 * Partitions have only one parent, so conflict should never
-				 * occur
+				 * Partitions have only one parent and have no column
+				 * definitions of their own, so conflict should never occur.
 				 */
 				Assert(!is_partition);
 
@@ -2118,8 +2118,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * Now that we have the column definition list for a partition, we can
-	 * check whether the columns referenced in column option specifications
-	 * actually exist.  Also, we merge the options into the corresponding
+	 * check whether the columns referenced in the column constraint specs
+	 * actually exist.  Also, we merge the constraints into the corresponding
 	 * column definitions.
 	 */
 	if (is_partition && list_length(saved_schema) > 0)
@@ -13351,8 +13351,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	}
 
 	/*
-	 * Invalidate the relcache so that the new partition is now included
-	 * in rel's partition descriptor.
+	 * Invalidate the parent's relcache so that the new partition is now
+	 * included its partition descriptor.
 	 */
 	CacheInvalidateRelcache(rel);
 
@@ -13414,8 +13414,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
-	 * Invalidate the relcache so that the partition is no longer included
-	 * in our partition descriptor.
+	 * Invalidate the parent's relcache so that the partition is no longer
+	 * included in its partition descriptor.
 	 */
 	CacheInvalidateRelcache(rel);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c0b58d1841..ec440b353d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1718,26 +1718,26 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (operation == CMD_INSERT &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		PartitionDispatch  *pd;
 		int					i,
 							j,
-							num_parted,
-							num_leaf_parts;
+							num_parted;
 		List			   *leaf_parts;
 		ListCell		   *cell;
 		ResultRelInfo	   *leaf_part_rri;
 
-		/* Form the partition node tree and lock partitions */
-		pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-											  &num_parted, &leaf_parts);
-		mtstate->mt_partition_dispatch_info = pd;
+		/* Get the tuple-routing information and lock partitions */
+		mtstate->mt_partition_dispatch_info =
+					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
+													 &num_parted,
+													 &leaf_parts);
 		mtstate->mt_num_dispatch = num_parted;
-		num_leaf_parts = list_length(leaf_parts);
-		mtstate->mt_num_partitions = num_leaf_parts;
+		mtstate->mt_num_partitions = list_length(leaf_parts);
 		mtstate->mt_partitions = (ResultRelInfo *)
-						palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+						palloc0(mtstate->mt_num_partitions *
+												sizeof(ResultRelInfo));
 		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
-					palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+						palloc0(mtstate->mt_num_partitions *
+												sizeof(TupleConversionMap *));
 
 		leaf_part_rri = mtstate->mt_partitions;
 		i = j = 0;
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index cec54ae62e..2c6b6cf3a0 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -7,7 +7,7 @@
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ * src/include/catalog/pg_partitioned_table.h
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -47,7 +47,7 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 #ifdef CATALOG_VARLEN
 	oidvector		partclass;		/* operator class to compare keys */
 	oidvector		partcollation;	/* user-specified collation for keys */
-	pg_node_tree	partexprs;		/* list of expressions in the partitioning
+	pg_node_tree	partexprs;		/* list of expressions in the partition
 									 * key; one item for each zero entry in
 									 * partattrs[] */
 #endif
-- 
2.11.0

0002-Miscallaneous-documentation-improvements.patchtext/x-diff; name=0002-Miscallaneous-documentation-improvements.patchDownload
From cd0856998198f59636f9cf4f34c25d01e089aea9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:06:39 +0900
Subject: [PATCH 2/5] Miscallaneous documentation improvements.

---
 doc/src/sgml/ref/alter_table.sgml  |  4 ++--
 doc/src/sgml/ref/create_table.sgml | 25 +++++++++++++------------
 doc/src/sgml/ref/insert.sgml       | 11 +++++++++++
 doc/src/sgml/ref/update.sgml       |  7 +++++++
 4 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index a6a43c4b30..333b01db36 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -715,7 +715,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+    <term><literal>ATTACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
@@ -1332,7 +1332,7 @@ ALTER TABLE measurement
    Attach a partition to list partitioned table:
 <programlisting>
 ALTER TABLE cities
-    ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+    ATTACH PARTITION cities_ab FOR VALUES IN ('a', 'b');
 </programlisting></para>
 
   <para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8bf8af302b..58f8bf6d6a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -248,7 +248,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
     <listitem>
      <para>
       Creates the table as <firstterm>partition</firstterm> of the specified
@@ -275,7 +275,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      <para>
       Rows inserted into a partitioned table will be automatically routed to
       the correct partition.  If no suitable partition exists, an error will
-      occur.
+      occur.  Also, if updating a row in a given partition causes it to move
+      to another partition due to the new partition key, an error will occur.
      </para>
 
      <para>
@@ -1477,7 +1478,6 @@ CREATE TABLE employees OF employee_type (
    Create a range partitioned table:
 <programlisting>
 CREATE TABLE measurement (
-    city_id         int not null,
     logdate         date not null,
     peaktemp        int,
     unitsales       int
@@ -1488,9 +1488,10 @@ CREATE TABLE measurement (
    Create a list partitioned table:
 <programlisting>
 CREATE TABLE cities (
+    city_id      bigserial not null,
     name         text not null,
-    population   int,
-) PARTITION BY LIST (initcap(name));
+    population   bigint,
+) PARTITION BY LIST (left(lower(name), 1));
 </programlisting></para>
 
   <para>
@@ -1498,30 +1499,30 @@ CREATE TABLE cities (
 <programlisting>
 CREATE TABLE measurement_y2016m07
     PARTITION OF measurement (
-    unitsales WITH OPTIONS DEFAULT 0
+    unitsales DEFAULT 0
 ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
 </programlisting></para>
 
   <para>
    Create partition of a list partitioned table:
 <programlisting>
-CREATE TABLE cities_west
+CREATE TABLE cities_ab
     PARTITION OF cities (
     CONSTRAINT city_id_nonzero CHECK (city_id != 0)
-) FOR VALUES IN ('Los Angeles', 'San Francisco');
+) FOR VALUES IN ('a', 'b');
 </programlisting></para>
 
   <para>
    Create partition of a list partitioned table that is itself further
    partitioned and then add a partition to it:
 <programlisting>
-CREATE TABLE cities_west
+CREATE TABLE cities_ab
     PARTITION OF cities (
     CONSTRAINT city_id_nonzero CHECK (city_id != 0)
-) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+) FOR VALUES IN ('a', 'b') PARTITION BY RANGE (population);
 
-CREATE TABLE cities_west_10000_to_100000
-    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
+CREATE TABLE cities_ab_10000_to_100000
+    PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index 06f416039b..00c984d8d5 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -526,6 +526,17 @@ INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</repl
    updated by the command.
   </para>
  </refsect1>
+ 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   If the specified table is a partitioned table, each row is routed to
+   the appropriate partition and inserted into it.  If the specified table
+   is a partition, an error will occur if one of the input rows violates
+   the partition constraint.
+  </para>
+ </refsect1>
 
  <refsect1>
   <title>Examples</title>
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 2de0f4aad1..e86993b9cf 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -279,6 +279,13 @@ UPDATE <replaceable class="parameter">count</replaceable>
    sub-selects is safer, though often harder to read and slower than
    using a join.
   </para>
+
+  <para>
+   In case of partitioned tables, updating a row might cause it to move
+   to a new partition due to the new partition key.  An error will occur
+   in this case.  Also, if the specified table is a partition, an error
+   will occur if the new row violates the partition constraint.
+  </para>
  </refsect1>
 
  <refsect1>
-- 
2.11.0

0003-Invalidate-the-parent-s-relcache-after-partition-cre.patchtext/x-diff; name=0003-Invalidate-the-parent-s-relcache-after-partition-cre.patchDownload
From 967c270b858ce52d45b7e0a8cfe7e0bf24cb1dbd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:06 +0900
Subject: [PATCH 3/5] Invalidate the parent's relcache after partition
 creation.

---
 src/backend/commands/tablecmds.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a9650114d4..f94cab60a3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -777,12 +777,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * it does not return on error.
 		 */
 		check_new_partition_bound(relname, parent, bound);
-		heap_close(parent, NoLock);
 
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, bound);
 
 		/*
+		 * We must invalidate the parent's relcache so that the next
+		 * CommandCounterIncrement() will cause the same to be rebuilt with
+		 * the new partition's info included in its partition descriptor.
+		 */
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);
+
+		/*
 		 * The code that follows may also update the pg_class tuple to update
 		 * relnumchecks, so bump up the command counter to avoid the "already
 		 * updated by self" error.
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 2127f8ad891e3967f00bb821331ae16ad29a342e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/5] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.
---
 src/backend/commands/copy.c            | 19 +++++++++++++++++--
 src/backend/commands/tablecmds.c       |  2 +-
 src/backend/executor/execMain.c        | 22 +++++++++++++++-------
 src/backend/executor/nodeModifyTable.c | 12 +++++++++++-
 src/include/executor/executor.h        |  3 +--
 5 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e9177491e2..b05055808f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1411,6 +1411,7 @@ BeginCopy(ParseState *pstate,
 			int				i,
 							num_parted;
 			ResultRelInfo  *leaf_part_rri;
+			List		   *partcheck = NIL;
 
 			/* Get the tuple-routing information and lock partitions */
 			cstate->partition_dispatch_info =
@@ -1426,6 +1427,15 @@ BeginCopy(ParseState *pstate,
 									palloc0(cstate->num_partitions *
 											sizeof(TupleConversionMap *));
 
+			/*
+			 * If the main target rel is a partition, ExecConstraints() as
+			 * applied to each leaf partition must consider its partition
+			 * constraint, because unlike explicit constraints, an implicit
+			 * partition constraint is not inherited.
+			 */
+			if (rel->rd_rel->relispartition)
+				partcheck = RelationGetPartitionQual(rel, true);
+
 			leaf_part_rri = cstate->partitions;
 			i = 0;
 			foreach(cell, leaf_parts)
@@ -1449,7 +1459,7 @@ BeginCopy(ParseState *pstate,
 				InitResultRelInfo(leaf_part_rri,
 								  partrel,
 								  1,	 /* dummy */
-								  false, /* no partition constraint check */
+								  partcheck,
 								  0);
 
 				/* Open partition indices */
@@ -2335,6 +2345,7 @@ CopyFrom(CopyState cstate)
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
+	List	   *partcheck = NIL;
 
 #define MAX_BUFFERED_TUPLES 1000
 	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
@@ -2451,6 +2462,10 @@ CopyFrom(CopyState cstate)
 		hi_options |= HEAP_INSERT_FROZEN;
 	}
 
+	/* Don't forget the partition constraints */
+	if (cstate->rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(cstate->rel, true);
+
 	/*
 	 * We need a ResultRelInfo so we can use the regular executor's
 	 * index-entry-making machinery.  (There used to be a huge amount of code
@@ -2460,7 +2475,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
+					  partcheck,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f94cab60a3..48adeedbe0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1329,7 +1329,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
+						  NIL,	/* No need for partition constraints */
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d43a204808..963cd2ae43 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -820,13 +820,19 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			Index		resultRelationIndex = lfirst_int(l);
 			Oid			resultRelationOid;
 			Relation	resultRelation;
+			List	   *partcheck = NIL;
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
+			/* Don't forget the partition constraint */
+			if (resultRelation->rd_rel->relispartition)
+				partcheck = RelationGetPartitionQual(resultRelation, true);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
+							  partcheck,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1216,7 +1222,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1254,10 +1260,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+	resultRelInfo->ri_PartitionCheck = partition_check;
 }
 
 /*
@@ -1284,6 +1287,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	ListCell   *l;
 	Relation	rel;
 	MemoryContext oldcontext;
+	List	   *partcheck = NIL;
 
 	/* First, search through the query result relations */
 	rInfo = estate->es_result_relations;
@@ -1312,6 +1316,10 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	 */
 	rel = heap_open(relid, NoLock);
 
+	/* Don't forget the partition constraint */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel, true);
+
 	/*
 	 * Make the new entry in the right context.
 	 */
@@ -1320,7 +1328,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
+					  partcheck,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ec440b353d..e236c0e0a7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1724,6 +1724,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		List			   *leaf_parts;
 		ListCell		   *cell;
 		ResultRelInfo	   *leaf_part_rri;
+		List			   *partcheck = NIL;
 
 		/* Get the tuple-routing information and lock partitions */
 		mtstate->mt_partition_dispatch_info =
@@ -1739,6 +1740,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 						palloc0(mtstate->mt_num_partitions *
 												sizeof(TupleConversionMap *));
 
+		/*
+		 * If the main target rel is a partition, ExecConstraints() as
+		 * applied to each leaf partition must consider its partition
+		 * constraint, because unlike explicit constraints, an implicit
+		 * partition constraint is not inherited.
+		 */
+		if (rel->rd_rel->relispartition)
+			partcheck = RelationGetPartitionQual(rel, true);
+
 		leaf_part_rri = mtstate->mt_partitions;
 		i = j = 0;
 		foreach(cell, leaf_parts)
@@ -1762,7 +1772,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			InitResultRelInfo(leaf_part_rri,
 							  partrel,
 							  1,		/* dummy */
-							  false,	/* no partition constraint checks */
+							  partcheck,
 							  eflags);
 
 			/* Open partition indices (note: ON CONFLICT unsupported)*/
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b4d09f9564..74213da078 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -73,7 +73,6 @@
 #define ExecEvalExpr(expr, econtext, isNull, isDone) \
 	((*(expr)->evalfunc) (expr, econtext, isNull, isDone))
 
-
 /* Hook for plugins to get control in ExecutorStart() */
 typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
 extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
@@ -189,7 +188,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
-- 
2.11.0

0005-Fix-a-tuple-routing-bug-in-multi-level-partitioned-t.patchtext/x-diff; name=0005-Fix-a-tuple-routing-bug-in-multi-level-partitioned-t.patchDownload
From e4b3652bbcda67c45d0699d23e7395a9d298f5d5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:12:33 +0900
Subject: [PATCH 5/5] Fix a tuple-routing bug in multi-level partitioned tables

Due to the bug, wrong index was assigned to the partitioned tables
below level 1.  Since we assign indexes in a breadth-first manner,
any partition of the next level should get assigned an index greater
than that of the last partition of the current level.
---
 src/backend/catalog/partition.c | 39 +++++++++++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index cc09fb3e55..f4a9525d1d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -950,7 +950,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			   *parted_rels;
 	ListCell   *lc;
 	int			i,
-				k;
+				k,
+				offset;
 
 	/*
 	 * Lock partitions and make a list of the partitioned ones to prepare
@@ -990,11 +991,19 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		 */
 	}
 
-	/* Generate PartitionDispatch objects for all partitioned tables */
+	/*
+	 * We want to create two arrays - one for leaf partitions and another for
+	 * partitioned tables (including the root table and internal partitions).
+	 * While we only create the latter here, leaf partition array of suitable
+	 * objects (such as, ResultRelInfo) is created by the caller using the
+	 * list of OIDs we return.  Indexes into these arrays get assigned in a
+	 * breadth-first manner, whereby partitions of any given level are placed
+	 * consecutively in the respective arrays.
+	 */
 	pd = (PartitionDispatchData **) palloc(*num_parted *
 										   sizeof(PartitionDispatchData *));
 	*leaf_part_oids = NIL;
-	i = k = 0;
+	i = k = offset = 0;
 	foreach(lc, parted_rels)
 	{
 		Relation	partrel = lfirst(lc);
@@ -1010,6 +1019,16 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		pd[i]->partdesc = partdesc;
 		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
 
+		/*
+		 * Indexes corresponding to the internal partitions are multiplied by
+		 * -1 to distinguish them from those of leaf partitions.  Encountering
+		 * an index >= 0 means we found a leaf partition, which is immediately
+		 * returned as the partition we are looking for.  A negative index
+		 * means we found a partitioned table, whose PartitionDispatch object
+		 * is located at the above index multiplied back by -1.  Using the
+		 * PartitionDispatch object, search is continued further down the
+		 * partition tree.
+		 */
 		m = 0;
 		for (j = 0; j < partdesc->nparts; j++)
 		{
@@ -1023,14 +1042,22 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			else
 			{
 				/*
-				 * We can assign indexes this way because of the way
-				 * parted_rels has been generated.
+				 * offset denotes the number of partitioned tables of upper
+				 * levels including those of the current level.  Any partition
+				 * of this table must belong to the next level and hence will
+				 * be placed after the last partitioned table of this level.
 				 */
-				pd[i]->indexes[j] = -(i + 1 + m);
+				pd[i]->indexes[j] = -(1 + offset + m);
 				m++;
 			}
 		}
 		i++;
+
+		/*
+		 * This counts the number of partitioned tables at upper levels
+		 * including those of the current level.
+		 */
+		offset += m;
 	}
 
 	return pd;
-- 
2.11.0

#202Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Tomas Vondra (#197)
Re: Declarative partitioning - another take

On 2016/12/13 0:17, Tomas Vondra wrote:

On 12/12/2016 07:37 AM, Amit Langote wrote:

Hi Tomas,

On 2016/12/12 10:02, Tomas Vondra wrote:

2) I'm wondering whether having 'table' in the catalog name (and also in
the new relkind) is too limiting. I assume we'll have partitioned indexes
one day, for example - do we expect to use the same catalogs?

I am not sure I understand your idea of partitioned indexes, but I doubt
it would require entries in the catalog under consideration. Could you
perhaps elaborate more?

OK, let me elaborate. Let's say we have a partitioned table, and I want to
create an index. The index may be either "global" i.e. creating a single
relation for data from all the partitions, or "local" (i.e. partitioned
the same way as the table).

Local indexes are easier to implement (it's essentially what we have now,
except that we need to create the indexes manually for each partition),
and don't work particularly well for some use cases (e.g. unique
constraints). This is what I mean by "partitioned indexes".

If the index is partitioned just like the table, we probably won't need to
copy the partition key info (so, nothing in pg_partitioned_table).
I'm not sure it makes sense to partition the index differently than the
table - I don't see a case where that would be useful.

The global indexes would work better for the unique constraint use case,
but it clearly contradicts our idea of TID (no information about which
partition that references).

So maybe the catalog really only needs to track info about tables? Not
sure. I'm just saying it'd be unfortunate to have _table in the name, and
end up using it for indexes too.

Hmm, I didn't quite think of the case where the index is partitioned
differently from the table, but perhaps that's possible with some other
databases.

What you describe as "local indexes" or "locally partitioned indexes" is
something I would like to see being pursued in the near term. In that
case, we would allow defining indexes on the parent that are recursively
defined on the partitions and marked as inherited index, just like we have
inherited check constraints and NOT NULL constraints. I have not studied
whether we could implement (globally) *unique* indexes with this scheme
though, wherein the index key is a superset of the partition key.

5) Half the error messages use 'child table' while the other half uses
'partition'. I think we should be using 'partition' as child tables really
have little meaning outside inheritance (which is kinda hidden behind the
new partitioning stuff).

One way to go about it may be to check all sites that can possibly report
an error involving child tables (aka "partitions") whether they know from
the context which name to use. I think it's possible, because we have
access to the parent relation in all such sites and looking at the relkind
can tell whether to call child tables "partitions".

Clearly, this is a consequence of building the partitioning on top of
inheritance (not objecting to that approach, merely stating a fact).

I'm fine with whatever makes the error messages more consistent, if it
does not make the code significantly more complex. It's a bit confusing
when some use 'child tables' and others 'partitions'. I suspect even a
single DML command may return a mix of those, depending on where exactly
it fails (old vs. new code).

So, we have mostly some old DDL (CREATE/ALTER TABLE) and maintenance
commands that understand inheritance. All of the their error messages
apply to partitions as well, wherein they will be referred to as "child
tables" using old terms. We now have some cases where the commands cause
additional error messages for only partitions because of additional
restrictions that apply to them. We use "partitions" for them because
they are essentially new error messages.

There won't be a case where single DML command would mix the two terms,
because we do not allow mixing partitioning and regular inheritance.
Maybe I misunderstood you though.

So if I understand the plan correctly, we first do a parallel scan of the
parent, then partition_1, partition_2 etc. But AFAIK we scan the tables in
Append sequentially, and each partition only has 1000 rows each, making
the parallel execution rather inefficient. Unless we scan the partitions
concurrently.

In any case, as this happens even with plain inheritance, it's probably
more about the parallel query than about the new partitioning patch.

Yes, I have seen some discussion [2] about a Parallel Append, which would
result in plans you're probably thinking of.

Actually, I think that thread is about allowing partial paths even if only
some appendrel members support it. My point is that in this case it's a
bit silly to even build the partial paths, when the partitions only have
1000 rows each.

I kinda suspect we only look at the total appendrel rowcount estimate, but
haven't checked.

Oh, I misread. Anyway, Parallel Append is something to think about.

3) The last issue I noticed is that while

EXPLAIN SELECT * FROM partitioned_table WHERE id = 1292323;

works just fine (it takes fair amount of time to plan with 10k partitions,
but that's expected), this

EXPLAIN UPDATE partitioned_table SET x = 1 WHERE id = 1292323;

allocates a lot of memory (~8GB on my laptop, before it gets killed by OOM
killer). Again, the same thing happens with plain inheritance-based
partitioning, so it's probably not a bug in the partitioning patch.

I'm mentioning it here because I think the new partitioning will hopefully
get more efficient and handle large partition counts more efficiently (the
inheritance only really works for ~100 partitions, which is probably why
no one complained about OOM during UPDATEs). Of course, 10k partitions is
a bit extreme (good for testing, though).

Plans with the inheritance parents as target tables (update/delete)
go through inheritance_planner() in the optimizer, which currently
has a design that is based on certain assumptions about traditional
inheritance. It's possible hopefully to make it less expensive for
the partitioned tables.

Yes, I know. I wasn't really reporting it as a bug in the partitioning
patch, but more as a rather surprising difference between plain SELECT and
UPDATE.

Am I right that one of the ambitions of the new partitioning is to improve
behavior with large number of partitions?

Yes. Currently, SELECT planning is O(n) with significantly large constant
factor. It is possible now to make it O(log n). Also, if we can do away
with inheritance_planner() treatment for the *partitioned tables* in case
of UPDATE/DELETE, then that would be great. That would mean their
planning time would be almost same as the SELECT case.

As you might know, we have volunteers to make this happen sooner [1]/messages/by-id/426b2b01-61e0-43aa-bd84-c6fcf516f1c3@postgrespro.ru, :)

At first I thought it's somewhat related to the FDW sharding (each node
being a partition and having local subpartitions), but I realize the
planner will only deal with the node partitions I guess.

Yeah, planner would only have the local partitioning metadata at its
disposal. Foreign tables can only be leaf partitions, which if need to be
scanned for a given query, will be scanned using a ForeignScan.

Thanks,
Amit

[1]: /messages/by-id/426b2b01-61e0-43aa-bd84-c6fcf516f1c3@postgrespro.ru
/messages/by-id/426b2b01-61e0-43aa-bd84-c6fcf516f1c3@postgrespro.ru

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

#203Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#188)
Re: Declarative partitioning - another take

On Thu, Dec 8, 2016 at 11:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hierarchical lock manager stuff is interesting. Are you perhaps alluding
to a new *intention* lock mode as described in the literature on multiple
granularity locking [1]?

Yes.

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

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

#204Robert Haas
robertmhaas@gmail.com
In reply to: Maksim Milyutin (#190)
Re: Declarative partitioning - another take

On Fri, Dec 9, 2016 at 5:46 AM, Maksim Milyutin
<m.milyutin@postgrespro.ru> wrote:

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the children
inherit it;

The first one has been implemented in pg_pathman somehow, but the code
relies on dirty hacks, so the FDW API has to be improved. As for the
extended index support, it doesn't look like a super-hard task.

Great!

I think that the second one will be fairly tricky. You will need some
way of linking the child indexes back to the parent index. And then
you will need to prohibit them from being dropped or modified
independent of the parent index. And you will need to cascade ALTER
commands on the parent index (which will have no real storage, I hope)
down to the children. Unfortunately, index names have to be unique on
a schema-wide basis, so we'll somehow need to generate names for the
"child" indexes. But those names might collide with existing objects,
and will need to be preserved across a dump-and-restore. The concept
is simple but getting all of the details right is hard.

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

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

#205Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#201)
Re: Declarative partitioning - another take

On Tue, Dec 13, 2016 at 1:58 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attaching the above patch, along with some other patches posted earlier,
and one more patch fixing another bug I found. Patch descriptions follow:

0001-Miscallaneous-code-and-comment-improvements.patch

Fixes some obsolete comments while improving others. Also, implements
some of Tomas Vondra's review comments.

Committed with some pgindenting.

0002-Miscallaneous-documentation-improvements.patch

Fixes inconsistencies and improves some examples in the documentation.
Also, mentions the limitation regarding row movement.

Ignored because I committed what I think is the same or a similar
patch earlier. Please resubmit any remaining changes.

0003-Invalidate-the-parent-s-relcache-after-partition-cre.patch

Fixes a bug reported by Tomas, whereby a parent's relcache was not
invalidated after creation of a new partition using CREATE TABLE PARTITION
OF. This resulted in tuple-routing code not being to able to find a
partition that was created by the last command in a given transaction.

Shouldn't StorePartitionBound() be responsible for issuing its own
invalidations, as StorePartitionKey() already is? Maybe you'd need to
pass "parent" as another argument, but that way you know you don't
have the same bug at some other place where the function is called.

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patch

Fixes a bug I found this morning, whereby an internal partition's
constraint would not be enforced if it is targeted directly. See example
below:

create table p (a int, b char) partition by range (a);
create table p1 partition of p for values from (1) to (10) partition by
list (b);
create table p1a partition of p1 for values in ('a');
insert into p1 values (0, 'a'); -- should fail, but doesn't

I expect I'm missing something here, but why do we need to hoist
RelationGetPartitionQual() out of InitResultRelInfo() instead of just
having BeginCopy() pass true instead of false?

(Also needs a rebase due to the pgindent cleanup.)

0005-Fix-a-tuple-routing-bug-in-multi-level-partitioned-t.patch

Fixes a bug discovered by Dmitry Ivanov, whereby wrong indexes were
assigned to the partitions of lower levels (level > 1), causing spurious
"partition not found" error as demonstrated in his email [1].

Committed. It might have been good to include a test case.

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

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

#206Ildar Musin
i.musin@postgrespro.ru
In reply to: Dmitry Ivanov (#181)
Re: Declarative partitioning - another take

Hi hackers,

On 08.12.2016 19:44, Dmitry Ivanov wrote:

That would be fantastic. I and my colleagues at EnterpriseDB can
surely help review; of course, maybe you and some of your colleagues
would like to help review our patches, too.

Certainly, I'll start reviewing as soon as I get familiar with the code.

Do you think this is
likely to be something where you can get something done quickly, with
the hope of getting it into v10?

Yes, I've just cleared my schedule in order to make this possible. I'll
bring in the patches ASAP.

We've noticed that PartitionDispatch object is built on every INSERT
query and that it could create unnecessary overhead. Wouldn't it be
better to keep it in relcache?

Thanks!

--
Ildar Musin
i.musin@postgrespro.ru

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

#207Robert Haas
robertmhaas@gmail.com
In reply to: Ildar Musin (#206)
Re: Declarative partitioning - another take

On Tue, Dec 13, 2016 at 12:22 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:

We've noticed that PartitionDispatch object is built on every INSERT query
and that it could create unnecessary overhead. Wouldn't it be better to keep
it in relcache?

You might be able to cache some of that data in the relcache, but List
*keystate is pointing to query-lifespan data, so you can't cache that.

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

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

#208Venkata B Nagothi
nag1010@gmail.com
In reply to: Amit Langote (#194)
Re: Declarative partitioning - another take

On Mon, Dec 12, 2016 at 3:06 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Hi,

On 2016/12/11 10:02, Venkata B Nagothi wrote:

On Fri, Dec 9, 2016 at 11:11 PM, Amit Langote <amitlangote09@gmail.com>
wrote:

On Fri, Dec 9, 2016 at 3:16 PM, Venkata B Nagothi <nag1010@gmail.com>
wrote:

I am testing the partitioning feature from the latest master and got

the

following error while loading the data -

db01=# create table orders_y1993 PARTITION OF orders FOR VALUES FROM
('1993-01-01') TO ('1993-12-31');
CREATE TABLE

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only

0 of

8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM

|Clerk#000001993|0|ithely

regular pack"

Hmm. Could you tell what relation the file/relfilenode 16412 belongs

to?

db01=# select relname from pg_class where relfilenode=16412 ;
relname
--------------
orders_y1997
(1 row)

I VACUUMED the partition and then re-ran the copy command and no luck.

db01=# vacuum orders_y1997;
VACUUM

db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM

|Clerk#000001993|0|ithely

regular pack"

I do not quite understand the below behaviour as well. I VACUUMED 1997
partition and then i got an error for 1992 partition and then after 1996
and then after 1994 and so on.

[ ... ]

db01=# vacuum orders_y1997;
VACUUM
db01=# copy orders from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16412": read only 0
of 8192 bytes
CONTEXT: COPY orders, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM

|Clerk#000001993|0|ithely

regular pack"
db01=#

Am i not understanding anything here ?

I could not reproduce this issue. Also, I could not say what might have
gone wrong based only on the information I have seen so far.

Have you tried inserting the same data using insert?

I can load the data into appropriate partitions using INSERT. So, no issues
there.

db01=# CREATE TABLE orders2(
o_orderkey INTEGER,
o_custkey INTEGER,
o_orderstatus CHAR(1),
o_totalprice REAL,
o_orderdate DATE,
o_orderpriority CHAR(15),
o_clerk CHAR(15),
o_shippriority INTEGER,
o_comment VARCHAR(79)) partition by (o_orderdate);

*db01=# insert into orders2 select * from orders where
o_orderdate='1995-10-11';*
*INSERT 0 3110*

create table orders_unpartitioned (like orders);
copy orders_unpartitioned from '/data/orders-1993.csv';
insert into orders select * from orders_unpartitioned;

Loading the data into a normal table is not an issue (infact the csv is
generated from the table itself)

The issue is occurring only when i am trying to load the data from CSV file
into a partitioned table -

db01=# CREATE TABLE orders_y1992
PARTITION OF orders2 FOR VALUES FROM ('1992-01-01') TO ('1992-12-31');
CREATE TABLE
db01=# copy orders2 from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16407": read only 0
of 8192 bytes
CONTEXT: COPY orders2, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"

Not sure why COPY is failing.

Regards,

Venkata B N
Database Consultant

#209Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Maksim Milyutin (#190)
Re: Declarative partitioning - another take

On 2016/12/09 19:46, Maksim Milyutin wrote:

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the
children inherit it;

The first one has been implemented in pg_pathman somehow, but the code
relies on dirty hacks, so the FDW API has to be improved. As for the
extended index support, it doesn't look like a super-hard task.

That would be great! I'd like to help review the first one.

Best regards,
Etsuro Fujita

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

#210Ildar Musin
i.musin@postgrespro.ru
In reply to: Robert Haas (#207)
Re: Declarative partitioning - another take

Hi,

On 13.12.2016 21:10, Robert Haas wrote:

On Tue, Dec 13, 2016 at 12:22 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:

We've noticed that PartitionDispatch object is built on every INSERT query
and that it could create unnecessary overhead. Wouldn't it be better to keep
it in relcache?

You might be able to cache some of that data in the relcache, but List
*keystate is pointing to query-lifespan data, so you can't cache that.

Yes, you are right. I meant mostly the 'indexes' field. I've measured
insert performance with perf in case when there are thousand partitions
and it seems that 34% of the time it takes to run
RelationGetPartitionDispatchInfo() which builds this indexes array. And
the most of the time it spends on acquiring locks on all partitions
which is unnecessary if we're inserting in just a single partition.
Probably we could avoid this by moving at least indexes field into cache.

--
Ildar Musin
i.musin@postgrespro.ru

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

#211Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Amit Langote (#202)
Re: Declarative partitioning - another take

Hi Amit,

On 12/13/2016 09:45 AM, Amit Langote wrote:

On 2016/12/13 0:17, Tomas Vondra wrote:

On 12/12/2016 07:37 AM, Amit Langote wrote:

Hi Tomas,

On 2016/12/12 10:02, Tomas Vondra wrote:

2) I'm wondering whether having 'table' in the catalog name (and also in
the new relkind) is too limiting. I assume we'll have partitioned indexes
one day, for example - do we expect to use the same catalogs?

I am not sure I understand your idea of partitioned indexes, but I doubt
it would require entries in the catalog under consideration. Could you
perhaps elaborate more?

OK, let me elaborate. Let's say we have a partitioned table, and I want to
create an index. The index may be either "global" i.e. creating a single
relation for data from all the partitions, or "local" (i.e. partitioned
the same way as the table).

Local indexes are easier to implement (it's essentially what we have now,
except that we need to create the indexes manually for each partition),
and don't work particularly well for some use cases (e.g. unique
constraints). This is what I mean by "partitioned indexes".

If the index is partitioned just like the table, we probably won't need to
copy the partition key info (so, nothing in pg_partitioned_table).
I'm not sure it makes sense to partition the index differently than the
table - I don't see a case where that would be useful.

The global indexes would work better for the unique constraint use case,
but it clearly contradicts our idea of TID (no information about which
partition that references).

So maybe the catalog really only needs to track info about tables? Not
sure. I'm just saying it'd be unfortunate to have _table in the name, and
end up using it for indexes too.

Hmm, I didn't quite think of the case where the index is partitioned
differently from the table, but perhaps that's possible with some other
databases.

I haven't thought about that very deeply either, so perhaps it's an
entirely silly idea. Also, probably quite complex to implement I guess,
so unlikely to be pursued soon.

What you describe as "local indexes" or "locally partitioned indexes" is
something I would like to see being pursued in the near term. In that
case, we would allow defining indexes on the parent that are recursively
defined on the partitions and marked as inherited index, just like we have
inherited check constraints and NOT NULL constraints. I have not studied
whether we could implement (globally) *unique* indexes with this scheme
though, wherein the index key is a superset of the partition key.

I think implementing UNIQUE constraint with local indexes is possible
and possibly even fairly simple, but it likely requires SHARE lock on
all partitions, which is not particularly nice.

When the partition key is referenced in the constraint, that may allow
locking only a subset of the partitions, possibly even a single one. But
with multi-level partitioning schemes that may be difficult.

Also, I don't think it's very likely to have the partitioning key as
part of the unique constraint. For example 'users' table is unlikely to
be distributed by 'login' and so on.

The global indexes make this easier, because there's just a single
index to check. But of course, attaching/detaching partitions gets more
expensive.

Anyway, starting a detailed discussion about local/global indexes was
not really what I meant to do.

Clearly, this is a consequence of building the partitioning on top of
inheritance (not objecting to that approach, merely stating a fact).

I'm fine with whatever makes the error messages more consistent, if it
does not make the code significantly more complex. It's a bit confusing
when some use 'child tables' and others 'partitions'. I suspect even a
single DML command may return a mix of those, depending on where exactly
it fails (old vs. new code).

So, we have mostly some old DDL (CREATE/ALTER TABLE) and maintenance
commands that understand inheritance. All of the their error messages
apply to partitions as well, wherein they will be referred to as "child
tables" using old terms. We now have some cases where the commands cause
additional error messages for only partitions because of additional
restrictions that apply to them. We use "partitions" for them because
they are essentially new error messages.

There won't be a case where single DML command would mix the two terms,
because we do not allow mixing partitioning and regular inheritance.
Maybe I misunderstood you though.

Don't we call inheritance-related functions from the new DDL? In that
case we'd fail with 'child tables' error messages in the old code, and
'partitions' in the new code. I'd be surprised if there was no such code
reuse, but I haven't checked.

Am I right that one of the ambitions of the new partitioning is to improve
behavior with large number of partitions?

Yes. Currently, SELECT planning is O(n) with significantly large constant
factor. It is possible now to make it O(log n). Also, if we can do away
with inheritance_planner() treatment for the *partitioned tables* in case
of UPDATE/DELETE, then that would be great. That would mean their
planning time would be almost same as the SELECT case.

As you might know, we have volunteers to make this happen sooner [1], :)

Yes, I know. And it's great that you've managed to make the first step,
getting all the infrastructure in, allowing others to build on that.
Kudos to you!

At first I thought it's somewhat related to the FDW sharding (each node
being a partition and having local subpartitions), but I realize the
planner will only deal with the node partitions I guess.

Yeah, planner would only have the local partitioning metadata at its
disposal. Foreign tables can only be leaf partitions, which if need to be
scanned for a given query, will be scanned using a ForeignScan.

Right, makes sense. Still, I can imagine for example having many daily
partitions and not having to merge them regularly just to reduce the
number of partitions.

regards

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

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

#212Dmitry Ivanov
d.ivanov@postgrespro.ru
In reply to: Robert Haas (#183)
1 attachment(s)
Re: Declarative partitioning - another take

Hi everyone,

Looks like "sql_inheritance" GUC is affecting partitioned tables:

explain (costs off) select * from test;
QUERY PLAN
------------------------------
Append
-> Seq Scan on test
-> Seq Scan on test_1
-> Seq Scan on test_2
-> Seq Scan on test_1_1
-> Seq Scan on test_1_2
-> Seq Scan on test_1_1_1
-> Seq Scan on test_1_2_1
(8 rows)

set sql_inheritance = off;

explain (costs off) select * from test;
QUERY PLAN
------------------
Seq Scan on test
(1 row)

I might be wrong, but IMO this should not happen. Queries involving update,
delete etc on partitioned tables are basically broken. Moreover, there's no
point in performing such operations on a parent table that's supposed to be
empty at all times.

I've come up with a patch which fixes this behavior for UPDATE, DELETE,
TRUNCATE and also in transformTableEntry(). It might be hacky, but it gives
an idea.

I didn't touch RenameConstraint() and renameatt() since this would break
ALTER TABLE ONLY command.

--
Dmitry Ivanov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

Attachments:

partitioning_inh_flag_v1.difftext/x-patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a574dc..67e118e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1183,7 +1183,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 	{
 		RangeVar   *rv = lfirst(cell);
 		Relation	rel;
-		bool		recurse = interpretInhOption(rv->inhOpt);
+		bool		recurse;
 		Oid			myrelid;
 
 		rel = heap_openrv(rv, AccessExclusiveLock);
@@ -1198,6 +1198,12 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
+		/* Use interpretInhOption() unless it's a partitioned table */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			recurse = interpretInhOption(rv->inhOpt);
+		else
+			recurse = true;
+
 		if (recurse)
 		{
 			ListCell   *child;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 5e65fe7..a3772f7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -367,6 +367,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
 	Node	   *qual;
+	RangeTblEntry *rte;
 
 	qry->commandType = CMD_DELETE;
 
@@ -384,6 +385,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 										 true,
 										 ACL_DELETE);
 
+	/* Set "inh" if table is partitioned */
+	rte = rt_fetch(qry->resultRelation, pstate->p_rtable);
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+		rte->inh = true;
+
 	/* grab the namespace item made by setTargetTable */
 	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
 
@@ -2164,6 +2170,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
 	Node	   *qual;
+	RangeTblEntry *rte;
 
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_insert = false;
@@ -2181,6 +2188,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 										 true,
 										 ACL_UPDATE);
 
+	/* Set "inh" if table is partitioned */
+	rte = rt_fetch(qry->resultRelation, pstate->p_rtable);
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+		rte->inh = true;
+
 	/* grab the namespace item made by setTargetTable */
 	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
 
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..215ec73 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -439,6 +439,10 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
 	rte = addRangeTableEntry(pstate, r, r->alias,
 							 interpretInhOption(r->inhOpt), true);
 
+	/* Set "inh" if table is partitioned */
+	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+		rte->inh = true;
+
 	return rte;
 }
 
#213Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#205)
7 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/14 1:32, Robert Haas wrote:

On Tue, Dec 13, 2016 at 1:58 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attaching the above patch, along with some other patches posted earlier,
and one more patch fixing another bug I found. Patch descriptions follow:

0001-Miscallaneous-code-and-comment-improvements.patch

Fixes some obsolete comments while improving others. Also, implements
some of Tomas Vondra's review comments.

Committed with some pgindenting.

Thanks!

0002-Miscallaneous-documentation-improvements.patch

Fixes inconsistencies and improves some examples in the documentation.
Also, mentions the limitation regarding row movement.

Ignored because I committed what I think is the same or a similar
patch earlier. Please resubmit any remaining changes.

It seems this patch is almost the same thing as what you committed.

0003-Invalidate-the-parent-s-relcache-after-partition-cre.patch

Fixes a bug reported by Tomas, whereby a parent's relcache was not
invalidated after creation of a new partition using CREATE TABLE PARTITION
OF. This resulted in tuple-routing code not being to able to find a
partition that was created by the last command in a given transaction.

Shouldn't StorePartitionBound() be responsible for issuing its own
invalidations, as StorePartitionKey() already is? Maybe you'd need to
pass "parent" as another argument, but that way you know you don't
have the same bug at some other place where the function is called.

OK, done that way in PATCH 1/7 (of the attached various patches as
described below).

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patch

Fixes a bug I found this morning, whereby an internal partition's
constraint would not be enforced if it is targeted directly. See example
below:

create table p (a int, b char) partition by range (a);
create table p1 partition of p for values from (1) to (10) partition by
list (b);
create table p1a partition of p1 for values in ('a');
insert into p1 values (0, 'a'); -- should fail, but doesn't

I expect I'm missing something here, but why do we need to hoist
RelationGetPartitionQual() out of InitResultRelInfo() instead of just
having BeginCopy() pass true instead of false?

(Also needs a rebase due to the pgindent cleanup.)

In this case, we want to enforce only the main target relation's partition
constraint (only needed if it happens to be an internal node partition),
not leaf partitions', because the latter is unnecessary.

We do InitResultRelInfo() for every leaf partition. What
RelationGetPartitionQual() would return to InitResultRelInfo() is the
partition constraint of the individual leaf partitions, which as just
mentioned is unnecessary, and also would be inefficient. With the
proposed patch, we only retrieve the partition constraint for the targeted
table (if there is any) once. However, when assigning to
ri_PartitionCheck of individual leaf partition's ResultRelInfo, we still
must map any Vars in the expression from the target tables's attnos to the
corresponding leaf partition's attnos (previous version of the patch
failed to do that).

0005-Fix-a-tuple-routing-bug-in-multi-level-partitioned-t.patch

Fixes a bug discovered by Dmitry Ivanov, whereby wrong indexes were
assigned to the partitions of lower levels (level > 1), causing spurious
"partition not found" error as demonstrated in his email [1].

Committed. It might have been good to include a test case.

Agreed, added tests in the attached patch PATCH 7/7.

Aside from the above, I found few other issues and fixed them in the
attached patches. Descriptions follow:

[PATCH 1/7] Invalidate the parent's relcache after partition creation.

Invalidate parent's relcache after a partition is created using CREATE
TABLE PARTITION OF. (Independent reports by Keith Fiske and David Fetter)

[PATCH 2/7] Change how RelationGetPartitionQual() and related code works

Since we always want to recurse, ie, include the parent's partition
constraint (if any), get rid of the argument recurse.

Refactor out the code doing the mapping of attnos of Vars in partition
constraint expressions (parent attnos -> child attnos). Move it to a
separate function map_partition_varattnos() and call it from appropriate
places. It previously used to be done in get_qual_from_partbound(),
which would lead to wrong results in certain multi-level partitioning
cases, as the mapping would be done for immediate parent-partition pairs.
Now in generate_partition_qual() which is the workhorse of
RelationGetPartitionQual(), we first generate the full expression
(considering all levels of partitioning) and then do the mapping from the
root parent to a leaf partition. It is also possible to generate
partition constraint up to certain non-leaf level and then apply the
same to leaf partitions of that sub-tree after suitable substitution
of varattnos using the new map_partition_varattnos() directly.

Bug fix: ATExecAttachPartition() failed to do the mapping when attaching
a partitioned table as partition. It is possible for the partitions of
such table to have different attributes from the table being attached
and/or the target partitioned table.

[PATCH 3/7] Refactor tuple-routing setup code

It's unnecessarily repeated in copy.c and nodeModifyTable.c, which makes
it a burden to maintain. Should've been like this to begin with.

I moved the common code to ExecSetupPartitionTupleRouting() in execMain.c
that also houses ExecFindParttion() currently. Hmm, should there be a
new src/backend/executor/execPartition.c?

[PATCH 4/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

InitResultRelInfo()'s API changes with this. Instead of passing
a boolean telling whether or not to load the partition constraint,
callers now need to pass the exact constraint expression to use
as ri_PartitionCheck or NIL.

[PATCH 5/7] Fix oddities of tuple-routing and TupleTableSlots

We must use the partition's tuple descriptor *after* a tuple is routed,
not the root table's. Partition's attributes, for example, may be
ordered diferently from the root table's.

We must then switch back to the root table's for the next tuple and
so on. A dedicated TupleTableSlot is allocated within EState called
es_partition_tuple_slot whose descriptor is set to a given leaf
partition for every row after it's routed.

[PATCH 6/7] Make ExecConstraints() emit the correct row in error msgs.

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's. If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root
table's rowtype and the partition's.

To convert back to the correct row format, keep root table relation
descriptor and a reverse tuple conversion map in the ResultRelInfo's
of leaf partitions.

[PATCH 7/7] Add some tests for recent fixes to PartitionDispatch code in
a25665088d

Thanks,
Amit

Attachments:

0001-Invalidate-the-parent-s-relcache-after-partition-cre.patchtext/x-diff; name=0001-Invalidate-the-parent-s-relcache-after-partition-cre.patchDownload
From 2720f5bbe4c29b42a36e19f4b740b2ce0dc3d474 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:06 +0900
Subject: [PATCH 1/7] Invalidate the parent's relcache after partition
 creation.

---
 src/backend/catalog/heap.c       |  7 ++++++-
 src/backend/commands/tablecmds.c | 13 ++++---------
 src/include/catalog/heap.h       |  2 +-
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c09c9f28a7..e5d6aecc3f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3230,9 +3230,12 @@ RemovePartitionKeyByRelId(Oid relid)
  * StorePartitionBound
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
+ *
+ * Also, invalidate the parent's relcache, so that the next rebuild will load
+ * the new partition's info into its partition descriptor.
  */
 void
-StorePartitionBound(Relation rel, Node *bound)
+StorePartitionBound(Relation rel, Relation parent, Node *bound)
 {
 	Relation	classRel;
 	HeapTuple	tuple,
@@ -3273,4 +3276,6 @@ StorePartitionBound(Relation rel, Node *bound)
 	CatalogUpdateIndexes(classRel, newtuple);
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
+
+	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a574dc50d..1c219b03dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -777,10 +777,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * it does not return on error.
 		 */
 		check_new_partition_bound(relname, parent, bound);
-		heap_close(parent, NoLock);
 
 		/* Update the pg_class entry. */
-		StorePartitionBound(rel, bound);
+		StorePartitionBound(rel, parent, bound);
+
+		heap_close(parent, NoLock);
 
 		/*
 		 * The code that follows may also update the pg_class tuple to update
@@ -13141,7 +13142,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 							  cmd->bound);
 
 	/* Update the pg_class entry. */
-	StorePartitionBound(attachRel, cmd->bound);
+	StorePartitionBound(attachRel, rel, cmd->bound);
 
 	/*
 	 * Generate partition constraint from the partition bound specification.
@@ -13352,12 +13353,6 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		}
 	}
 
-	/*
-	 * Invalidate the parent's relcache so that the new partition is now
-	 * included its partition descriptor.
-	 */
-	CacheInvalidateRelcache(rel);
-
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
 	/* keep our lock until commit */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 77dc1983e8..0e4262f611 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,6 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
-extern void StorePartitionBound(Relation rel, Node *bound);
+extern void StorePartitionBound(Relation rel, Relation parent, Node *bound);
 
 #endif   /* HEAP_H */
-- 
2.11.0

0002-Change-how-RelationGetPartitionQual-and-related-code.patchtext/x-diff; name=0002-Change-how-RelationGetPartitionQual-and-related-code.patchDownload
From ebd4bce03ebf2b0517e6e66f120af30c18990d72 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 2/7] Change how RelationGetPartitionQual() and related code
 works

Since we always want to recurse, ie, include the parent's partition
constraint (if any), get rid of the argument recurse.

Refactor out the code doing the mapping of attnos of Vars in partition
constraint expressions (parent attnos -> child attnos).  Move it to a
separate function map_partition_varattnos() and call it from appropriate
places.  It previously used to be done in get_qual_from_partbound(),
which would lead to wrong results in certain multi-level partitioning
cases, as the mapping would be done for immediate parent-partition pairs.
Now in generate_partition_qual() which is the workhorse of
RelationGetPartitionQual(), we first generate the full expression
(considering all levels of partitioning) and then do the mapping from the
root parent to a leaf partition.  It is also possible to generate
partition constraint up to certain non-leaf level and then apply the
same to leaf partitions of that sub-tree after suitable substitution
of varattnos using the new map_partition_varattnos() directly.

Bug fix: ATExecAttachPartition() failed to do the mapping when attaching
a partitioned table as partition. It is possible for the partitions of
such table to have different attributes from the table being attached
and/or the target partitioned table.
---
 src/backend/catalog/partition.c      | 100 +++++++++++++++++++----------------
 src/backend/commands/tablecmds.c     |   9 ++--
 src/backend/executor/execMain.c      |   3 +-
 src/backend/optimizer/util/plancat.c |   2 +-
 src/include/catalog/partition.h      |   3 +-
 5 files changed, 65 insertions(+), 52 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582b77..dfb9006422 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,48 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +920,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /* Turn an array of OIDs with N elements into a list */
@@ -1445,7 +1451,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1459,6 +1465,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1466,13 +1476,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
-
-		heap_close(parent, AccessShareLock);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
@@ -1492,18 +1499,21 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/*
+	 * Translate vars in the generated expression to have correct attnos. Note
+	 * that the vars in result 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.
+	 */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* the partition's qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..d2e4cfc365 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13323,6 +13323,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13345,8 +13346,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d43a204808..520fe4e0ce 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1256,8 +1256,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+								RelationGetPartitionQual(resultRelationDesc);
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf87b..6ff821e6cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -70,7 +70,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
-- 
2.11.0

0003-Refactor-tuple-routing-setup-code.patchtext/x-diff; name=0003-Refactor-tuple-routing-setup-code.patchDownload
From f117399bd18b7a47e94f616a158431c0d2ff66a5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 15:56:27 +0900
Subject: [PATCH 3/7] Refactor tuple-routing setup code

It's unnecessarily repeated in copy.c and nodeModifyTable.c, which makes
it a burden to maintain.  Should've been like this to begin with.

I moved the common code to ExecSetupPartitionTupleRouting() in execMain.c
that also houses ExecFindParttion() currently.  Hmm, should there be a
new src/backend/executor/execPartition.c?
---
 src/backend/commands/copy.c            | 72 ++++++----------------------
 src/backend/executor/execMain.c        | 85 ++++++++++++++++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c | 76 ++++++------------------------
 src/include/executor/executor.h        |  5 ++
 4 files changed, 120 insertions(+), 118 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a8da338f0..d5901651db 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1406,64 +1406,22 @@ BeginCopy(ParseState *pstate,
 		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			List	   *leaf_parts;
-			ListCell   *cell;
-			int			i,
-						num_parted;
-			ResultRelInfo *leaf_part_rri;
-
-			/* Get the tuple-routing information and lock partitions */
-			cstate->partition_dispatch_info =
-				RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-												 &num_parted,
-												 &leaf_parts);
+			PartitionDispatch  *partition_dispatch_info;
+			ResultRelInfo	   *partitions;
+			TupleConversionMap **partition_tupconv_maps;
+			int					num_parted,
+								num_partitions;
+
+			ExecSetupPartitionTupleRouting(rel,
+										   &partition_dispatch_info,
+										   &partitions,
+										   &partition_tupconv_maps,
+										   &num_parted, &num_partitions);
+			cstate->partition_dispatch_info = partition_dispatch_info;
 			cstate->num_dispatch = num_parted;
-			cstate->num_partitions = list_length(leaf_parts);
-			cstate->partitions = (ResultRelInfo *)
-				palloc(cstate->num_partitions *
-					   sizeof(ResultRelInfo));
-			cstate->partition_tupconv_maps = (TupleConversionMap **)
-				palloc0(cstate->num_partitions *
-						sizeof(TupleConversionMap *));
-
-			leaf_part_rri = cstate->partitions;
-			i = 0;
-			foreach(cell, leaf_parts)
-			{
-				Relation	partrel;
-
-				/*
-				 * We locked all the partitions above including the leaf
-				 * partitions.  Note that each of the relations in
-				 * cstate->partitions will be closed by CopyFrom() after it's
-				 * finished with its processing.
-				 */
-				partrel = heap_open(lfirst_oid(cell), NoLock);
-
-				/*
-				 * Verify result relation is a valid target for the current
-				 * operation.
-				 */
-				CheckValidResultRel(partrel, CMD_INSERT);
-
-				InitResultRelInfo(leaf_part_rri,
-								  partrel,
-								  1,	/* dummy */
-								  false,		/* no partition constraint
-												 * check */
-								  0);
-
-				/* Open partition indices */
-				ExecOpenIndices(leaf_part_rri, false);
-
-				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
-					cstate->partition_tupconv_maps[i] =
-						convert_tuples_by_name(tupDesc,
-											   RelationGetDescr(partrel),
-								 gettext_noop("could not convert row type"));
-				leaf_part_rri++;
-				i++;
-			}
+			cstate->partitions = partitions;
+			cstate->num_partitions = num_partitions;
+			cstate->partition_tupconv_maps = partition_tupconv_maps;
 		}
 	}
 	else
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 520fe4e0ce..b3cedadce4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "tcop/utility.h"
@@ -2998,6 +2999,90 @@ EvalPlanQualEnd(EPQState *epqstate)
 }
 
 /*
+ * ExecSetupPartitionTupleRouting - set up information needed during
+ * tuple routing for partitioned tables
+ *
+ * Output arguments:
+ * 'pd' receives an array of PartitionDispatch objects with one entry for
+ *		every partitioned table in the partition tree
+ * 'partitions' receives an array of ResultRelInfo objects with one entry for
+ *		every leaf partition in the partition tree
+ * 'tup_conv_maps' receives an array of TupleConversionMap objects with one
+ *		entry for every leaf partition (required to convert input tuple based
+ *		on the root table's rowtype to a leaf partition's rowtype after tuple
+ *		routing is done
+ * 'num_parted' receives the number of partitioned tables in the partition
+ *		tree (= the number of entries in the 'pd' output array)
+ * 'num_partitions' receives the number of leaf partitions in the partition
+ *		tree (= the number of entries in the 'partitions' and 'tup_conv_maps'
+ *		output arrays
+ *
+ * Note that all the relations in the partition tree are locked using the
+ * RowExclusiveLock mode upon return from this function.
+ */
+void
+ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions)
+{
+	TupleDesc	tupDesc = RelationGetDescr(rel);
+	List	   *leaf_parts;
+	ListCell   *cell;
+	int			i;
+	ResultRelInfo *leaf_part_rri;
+
+	/* Get the tuple-routing information and lock partitions */
+	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
+										   &leaf_parts);
+	*num_partitions = list_length(leaf_parts);
+	*partitions = (ResultRelInfo *) palloc(*num_partitions *
+										   sizeof(ResultRelInfo));
+	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
+										   sizeof(TupleConversionMap *));
+
+	leaf_part_rri = *partitions;
+	i = 0;
+	foreach(cell, leaf_parts)
+	{
+		Relation	partrel;
+		TupleDesc	part_tupdesc;
+
+		/*
+		 * We locked all the partitions above including the leaf partitions.
+		 * Note that each of the relations in *partitions are eventually
+		 * closed by the caller.
+		 */
+		partrel = heap_open(lfirst_oid(cell), NoLock);
+		part_tupdesc = RelationGetDescr(partrel);
+
+		/*
+		 * Verify result relation is a valid target for the current operation.
+		 */
+		CheckValidResultRel(partrel, CMD_INSERT);
+
+		/*
+		 * Save a tuple conversion map to convert a tuple routed to this
+		 * partition from the parent's type to the partition's.
+		 */
+		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+								 gettext_noop("could not convert row type"));
+
+		InitResultRelInfo(leaf_part_rri,
+						  partrel,
+						  1,	 /* dummy */
+						  false,
+						  0);
+
+		/* Open partition indices */
+		ExecOpenIndices(leaf_part_rri, false);
+		leaf_part_rri++;
+		i++;
+	}
+}
+
+/*
  * ExecFindPartition -- Find a leaf partition in the partition tree rooted
  * at parent, for the heap tuple contained in *slot
  *
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ec440b353d..a9546106ce 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1718,68 +1718,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (operation == CMD_INSERT &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		int					i,
-							j,
-							num_parted;
-		List			   *leaf_parts;
-		ListCell		   *cell;
-		ResultRelInfo	   *leaf_part_rri;
-
-		/* Get the tuple-routing information and lock partitions */
-		mtstate->mt_partition_dispatch_info =
-					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-													 &num_parted,
-													 &leaf_parts);
+		PartitionDispatch  *partition_dispatch_info;
+		ResultRelInfo	   *partitions;
+		TupleConversionMap **partition_tupconv_maps;
+		int					num_parted,
+							num_partitions;
+
+		ExecSetupPartitionTupleRouting(rel,
+									   &partition_dispatch_info,
+									   &partitions,
+									   &partition_tupconv_maps,
+									   &num_parted, &num_partitions);
+		mtstate->mt_partition_dispatch_info = partition_dispatch_info;
 		mtstate->mt_num_dispatch = num_parted;
-		mtstate->mt_num_partitions = list_length(leaf_parts);
-		mtstate->mt_partitions = (ResultRelInfo *)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(ResultRelInfo));
-		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(TupleConversionMap *));
-
-		leaf_part_rri = mtstate->mt_partitions;
-		i = j = 0;
-		foreach(cell, leaf_parts)
-		{
-			Oid			partrelid = lfirst_oid(cell);
-			Relation	partrel;
-
-			/*
-			 * We locked all the partitions above including the leaf
-			 * partitions.  Note that each of the relations in
-			 * mtstate->mt_partitions will be closed by ExecEndModifyTable().
-			 */
-			partrel = heap_open(partrelid, NoLock);
-
-			/*
-			 * Verify result relation is a valid target for the current
-			 * operation
-			 */
-			CheckValidResultRel(partrel, CMD_INSERT);
-
-			InitResultRelInfo(leaf_part_rri,
-							  partrel,
-							  1,		/* dummy */
-							  false,	/* no partition constraint checks */
-							  eflags);
-
-			/* Open partition indices (note: ON CONFLICT unsupported)*/
-			if (partrel->rd_rel->relhasindex && operation != CMD_DELETE &&
-				leaf_part_rri->ri_IndexRelationDescs == NULL)
-				ExecOpenIndices(leaf_part_rri, false);
-
-			if (!equalTupleDescs(RelationGetDescr(rel),
-								 RelationGetDescr(partrel)))
-				mtstate->mt_partition_tupconv_maps[i] =
-							convert_tuples_by_name(RelationGetDescr(rel),
-												   RelationGetDescr(partrel),
-								  gettext_noop("could not convert row type"));
-
-			leaf_part_rri++;
-			i++;
-		}
+		mtstate->mt_partitions = partitions;
+		mtstate->mt_num_partitions = num_partitions;
+		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
 	}
 
 	/*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b4d09f9564..bd1bc6bb6e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -213,6 +213,11 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern void ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions);
 extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
 				  TupleTableSlot *slot,
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 282f08bfab695b84baa4f311623ee8bba8ef1776 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

InitResultRelInfo()'s API changes with this.  Instead of passing
a boolean telling whether or not to load the partition constraint,
callers now need to pass the exact constraint expression to use
as ri_PartitionCheck or NIL.
---
 src/backend/commands/copy.c          |  7 +++++-
 src/backend/commands/tablecmds.c     |  2 +-
 src/backend/executor/execMain.c      | 43 ++++++++++++++++++++++++++++++------
 src/include/executor/executor.h      |  3 +--
 src/test/regress/expected/insert.out |  4 ++++
 src/test/regress/sql/insert.sql      |  3 +++
 6 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d5901651db..a0eb4241e2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2294,6 +2294,7 @@ CopyFrom(CopyState cstate)
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
+	List	   *partcheck = NIL;
 
 #define MAX_BUFFERED_TUPLES 1000
 	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
@@ -2410,6 +2411,10 @@ CopyFrom(CopyState cstate)
 		hi_options |= HEAP_INSERT_FROZEN;
 	}
 
+	/* Don't forget the partition constraints */
+	if (cstate->rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(cstate->rel);
+
 	/*
 	 * We need a ResultRelInfo so we can use the regular executor's
 	 * index-entry-making machinery.  (There used to be a huge amount of code
@@ -2419,7 +2424,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
+					  partcheck,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2e4cfc365..4bd4ec4e8c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
+						  NIL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3cedadce4..5627378a35 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -821,13 +821,19 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			Index		resultRelationIndex = lfirst_int(l);
 			Oid			resultRelationOid;
 			Relation	resultRelation;
+			List	   *partcheck = NIL;
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
+			/* Don't forget the partition constraint */
+			if (resultRelation->rd_rel->relispartition)
+				partcheck = RelationGetPartitionQual(resultRelation);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
+							  partcheck,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1217,7 +1223,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1255,9 +1261,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-								RelationGetPartitionQual(resultRelationDesc);
+	resultRelInfo->ri_PartitionCheck = partition_check;
 }
 
 /*
@@ -1284,6 +1288,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	ListCell   *l;
 	Relation	rel;
 	MemoryContext oldcontext;
+	List	   *partcheck = NIL;
 
 	/* First, search through the query result relations */
 	rInfo = estate->es_result_relations;
@@ -1312,6 +1317,10 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	 */
 	rel = heap_open(relid, NoLock);
 
+	/* Don't forget the partition constraint */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	/*
 	 * Make the new entry in the right context.
 	 */
@@ -1320,7 +1329,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
+					  partcheck,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -3032,6 +3041,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	ListCell   *cell;
 	int			i;
 	ResultRelInfo *leaf_part_rri;
+	List		  *partcheck = NIL;
 
 	/* Get the tuple-routing information and lock partitions */
 	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
@@ -3042,12 +3052,22 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
 										   sizeof(TupleConversionMap *));
 
+	/*
+	 * If the main target rel is a partition, ExecConstraints() as applied to
+	 * each leaf partition must consider its partition constraint, because
+	 * unlike explicit constraints, an implicit partition constraint is not
+	 * inherited.
+	 */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	leaf_part_rri = *partitions;
 	i = 0;
 	foreach(cell, leaf_parts)
 	{
 		Relation	partrel;
 		TupleDesc	part_tupdesc;
+		List	   *my_check = NIL;
 
 		/*
 		 * We locked all the partitions above including the leaf partitions.
@@ -3069,10 +3089,19 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
 								 gettext_noop("could not convert row type"));
 
+		/*
+		 * This is the parent's partition constraint, so any Vars in
+		 * it bear the its attribute numbers.  We must switch them to
+		 * the leaf partition's, which is possible with the
+		 * reverse_map's attribute-number map.
+		 */
+		if (partcheck)
+			my_check = map_partition_varattnos(partcheck, partrel, rel);
+
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
+						  my_check,
 						  0);
 
 		/* Open partition indices */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bd1bc6bb6e..8bcc876809 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -73,7 +73,6 @@
 #define ExecEvalExpr(expr, econtext, isNull, isDone) \
 	((*(expr)->evalfunc) (expr, econtext, isNull, isDone))
 
-
 /* Hook for plugins to get control in ExecutorStart() */
 typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
 extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
@@ -189,7 +188,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 561cefa3c4..95a7c4da7a 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,10 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- fail due to partition constraint failure
+insert into part_ee_ff values ('gg', 1);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (gg, 1).
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 846bb5897a..77577682ac 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,9 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- fail due to partition constraint failure
+insert into part_ee_ff values ('gg', 1);
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0005-Fix-oddities-of-tuple-routing-and-TupleTableSlots.patchtext/x-diff; name=0005-Fix-oddities-of-tuple-routing-and-TupleTableSlots.patchDownload
From b69f8be6f87afa5afdfea8a776955161c1da3e87 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 17:39:19 +0900
Subject: [PATCH 5/7] Fix oddities of tuple-routing and TupleTableSlots

We must use the partition's tuple descriptor *after* a tuple is routed,
not the root table's.  Partition's attributes, for example, may be
ordered diferently from the root table's.

We must then switch back to the root table's for the next tuple and
so on.  A dedicated TupleTableSlot is allocated within EState called
es_partition_tuple_slot whose descriptor is set to a given leaf
partition for every row after it's routed.
---
 src/backend/commands/copy.c            | 28 +++++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c | 25 +++++++++++++++++++++++++
 src/include/nodes/execnodes.h          |  3 +++
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a0eb4241e2..bec8c73903 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2441,6 +2441,15 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
+	 * Initialize a dedicated slot to manipulate tuples of any given
+	 * partition's rowtype.
+	 */
+	if (cstate->partition_dispatch_info)
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+	else
+		estate->es_partition_tuple_slot = NULL;
+
+	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2489,7 +2498,8 @@ CopyFrom(CopyState cstate)
 
 	for (;;)
 	{
-		TupleTableSlot *slot;
+		TupleTableSlot *slot,
+					   *oldslot = NULL;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2576,7 +2586,19 @@ CopyFrom(CopyState cstate)
 			map = cstate->partition_tupconv_maps[leaf_part_index];
 			if (map)
 			{
+				Relation	partrel = resultRelInfo->ri_RelationDesc;
+
 				tuple = do_convert_tuple(tuple, map);
+
+				/*
+				 * We must use the partition's tuple descriptor from this
+				 * point on.  Use a dedicated slot from this point on until
+				 * we're finished dealing with the partition.
+				 */
+				oldslot = slot;
+				slot = estate->es_partition_tuple_slot;
+				Assert(slot != NULL);
+				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 			}
 
@@ -2672,6 +2694,10 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
+
+				/* Switch back to the slot corresponding to the root table */
+				Assert(oldslot != NULL);
+				slot = oldslot;
 			}
 		}
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a9546106ce..da4c96a863 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,6 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	TupleTableSlot *oldslot = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -318,7 +319,19 @@ ExecInsert(ModifyTableState *mtstate,
 		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
 		if (map)
 		{
+			Relation partrel = resultRelInfo->ri_RelationDesc;
+
 			tuple = do_convert_tuple(tuple, map);
+
+			/*
+			 * We must use the partition's tuple descriptor from this
+			 * point on, until we're finished dealing with the partition.
+			 * Use the dedicated slot for that.
+			 */
+			oldslot = slot;
+			slot = estate->es_partition_tuple_slot;
+			Assert(slot != NULL);
+			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 		}
 	}
@@ -566,6 +579,10 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
+
+		/* Switch back to the slot corresponding to the root table */
+		Assert(oldslot != NULL);
+		slot = oldslot;
 	}
 
 	/*
@@ -1734,7 +1751,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
+
+		/*
+		 * Initialize a dedicated slot to manipulate tuples of any given
+		 * partition's rowtype.
+		 */
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
 	}
+	else
+		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1de5c8196d..be9a5e23cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -384,6 +384,9 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
+	/* Slot used to manipulate a tuple after it is routed to a partition */
+	TupleTableSlot *es_partition_tuple_slot;
+
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
-- 
2.11.0

0006-Make-ExecConstraints-emit-the-correct-row-in-error-m.patchtext/x-diff; name=0006-Make-ExecConstraints-emit-the-correct-row-in-error-m.patchDownload
From aa748c29286f916a286ff25ef25f0e2caa057058 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 18:00:47 +0900
Subject: [PATCH 6/7] Make ExecConstraints() emit the correct row in error
 msgs.

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root
table's rowtype and the partition's.

To convert back to the correct row format, keep root table relation
descriptor and a reverse tuple conversion map in the ResultRelInfo's
of leaf partitions.
---
 src/backend/commands/copy.c          |  2 +-
 src/backend/commands/tablecmds.c     |  2 +-
 src/backend/executor/execMain.c      | 83 ++++++++++++++++++++++++++++++++----
 src/include/executor/executor.h      |  2 +
 src/include/nodes/execnodes.h        |  2 +
 src/test/regress/expected/insert.out | 15 ++++++-
 src/test/regress/sql/insert.sql      | 11 +++++
 7 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index bec8c73903..9cd84e80c7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2424,7 +2424,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  partcheck,
+					  partcheck, NULL, NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4bd4ec4e8c..67ff1715ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  NIL,
+						  NIL, NULL, NULL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5627378a35..085a209882 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -833,7 +833,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  partcheck,
+							  partcheck, NULL, NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1224,6 +1224,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1262,6 +1264,12 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
 	resultRelInfo->ri_PartitionCheck = partition_check;
+	/*
+	 * Following fields are only looked at in some tuple-routing cases.
+	 * In other case, they are set to NULL.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
+	resultRelInfo->ri_PartitionReverseMap = partition_reverse_map;
 }
 
 /*
@@ -1329,7 +1337,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  partcheck,
+					  partcheck, NULL, NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1775,6 +1783,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * In case where the tuple is routed, it's been converted
+				 * to the partition's rowtype, which might differ from the
+				 * root table's.  We must convert it back to the root table's
+				 * type so that it's shown correctly in the error message.
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+					Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+					tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1788,9 +1816,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1802,6 +1830,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+				tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1814,9 +1856,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1824,6 +1866,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+			Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+			tuple = do_convert_tuple(tuple,
+									 resultRelInfo->ri_PartitionReverseMap);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1836,7 +1892,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3068,6 +3124,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		Relation	partrel;
 		TupleDesc	part_tupdesc;
 		List	   *my_check = NIL;
+		TupleConversionMap	*reverse_map;
 
 		/*
 		 * We locked all the partitions above including the leaf partitions.
@@ -3098,10 +3155,20 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		if (partcheck)
 			my_check = map_partition_varattnos(partcheck, partrel, rel);
 
+		/*
+		 * We must save a reverse tuple conversion map as well, to show the
+		 * correct input tuple in the error message shown by ExecConstraints()
+		 * in case of routed tuples.  Remember that at the point of failure,
+		 * the tuple has been converted to the partition's type which might
+		 * not match the input tuple.
+		 */
+		reverse_map = convert_tuples_by_name(part_tupdesc, tupDesc,
+								 gettext_noop("could not convert row type"));
+
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  my_check,
+						  my_check, rel, reverse_map,
 						  0);
 
 		/* Open partition indices */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8bcc876809..436d6937a9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,6 +189,8 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index be9a5e23cb..fadea14681 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,8 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
+	TupleConversionMap *ri_PartitionReverseMap;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 95a7c4da7a..328f776dd7 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -289,6 +289,18 @@ select tableoid::regclass, * from list_parted;
 insert into part_ee_ff values ('gg', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (gg, 1).
+-- check that correct row is shown in the error message if constraint fails
+-- after a tuple is routed to a partition with different rowtype from the
+-- root table
+create table part_ee_ff3 (like part_ee_ff);
+alter table part_ee_ff3 drop a;
+alter table part_ee_ff3 add a text; -- (a's attnum is now 3)
+alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30);
+truncate part_ee_ff;
+alter table part_ee_ff add constraint check_b_25 check (b = 25);
+insert into list_parted values ('ee', 20);
+ERROR:  new row for relation "part_ee_ff3" violates check constraint "check_b_25"
+DETAIL:  Failing row contains (ee, 20).
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -297,10 +309,11 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 7 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 77577682ac..ebe371e884 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -170,6 +170,17 @@ select tableoid::regclass, * from list_parted;
 -- fail due to partition constraint failure
 insert into part_ee_ff values ('gg', 1);
 
+-- check that correct row is shown in the error message if constraint fails
+-- after a tuple is routed to a partition with different rowtype from the
+-- root table
+create table part_ee_ff3 (like part_ee_ff);
+alter table part_ee_ff3 drop a;
+alter table part_ee_ff3 add a text; -- (a's attnum is now 3)
+alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30);
+truncate part_ee_ff;
+alter table part_ee_ff add constraint check_b_25 check (b = 25);
+insert into list_parted values ('ee', 20);
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0007-Add-some-tests-for-recent-fixes-to-PartitionDispatch.patchtext/x-diff; name=0007-Add-some-tests-for-recent-fixes-to-PartitionDispatch.patchDownload
From 941b1e23303eb934492978caa025a785adedc832 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 16 Dec 2016 09:45:57 +0900
Subject: [PATCH 7/7] Add some tests for recent fixes to PartitionDispatch code
 in a25665088d

---
 src/test/regress/expected/insert.out | 40 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 19 +++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 328f776dd7..aaa74da607 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -301,6 +301,36 @@ alter table part_ee_ff add constraint check_b_25 check (b = 25);
 insert into list_parted values ('ee', 20);
 ERROR:  new row for relation "part_ee_ff3" violates check constraint "check_b_25"
 DETAIL:  Failing row contains (ee, 20).
+alter table part_ee_ff drop constraint check_b_25;
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff4 partition of part_ee_ff for values from (30) to (40) partition by range (b);
+create table part_ee_ff4_1 partition of part_ee_ff4 for values from (30) to (35);
+create table part_ee_ff4_2 partition of part_ee_ff4 for values from (35) to (40);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 39) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3   | Ff |    20 |    29
+ part_ee_ff4_1 | Ff |    30 |    34
+ part_ee_ff4_2 | Ff |    35 |    39
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(10 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -309,7 +339,7 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 7 other objects
+NOTICE:  drop cascades to 15 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
@@ -317,3 +347,11 @@ drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
 drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff4
+drop cascades to table part_ee_ff4_1
+drop cascades to table part_ee_ff4_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index ebe371e884..0fa9cef4b5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -180,6 +180,25 @@ alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30)
 truncate part_ee_ff;
 alter table part_ee_ff add constraint check_b_25 check (b = 25);
 insert into list_parted values ('ee', 20);
+alter table part_ee_ff drop constraint check_b_25;
+
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff4 partition of part_ee_ff for values from (30) to (40) partition by range (b);
+create table part_ee_ff4_1 partition of part_ee_ff4 for values from (30) to (35);
+create table part_ee_ff4_2 partition of part_ee_ff4 for values from (35) to (40);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 39) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
 
 -- cleanup
 drop table range_parted cascade;
-- 
2.11.0

#214Greg Stark
stark@mit.edu
In reply to: Amit Langote (#213)
Re: Declarative partitioning - another take

Just poking around with partitioning. I notice that "\d parent"
doesn't list all the partitions, suggesting to use \d+ but a plain
"\d" does indeed list the partitions. That seems a bit strange and
also probably impractical if you have hundreds or thousands of
partitions. Has this come up in previous discussions? Unfortunately
it's proving a bit hard to search for "\d" :/

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

#215Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#213)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/16 17:02, Amit Langote wrote:

[PATCH 2/7] Change how RelationGetPartitionQual() and related code works

Since we always want to recurse, ie, include the parent's partition
constraint (if any), get rid of the argument recurse.

Refactor out the code doing the mapping of attnos of Vars in partition
constraint expressions (parent attnos -> child attnos). Move it to a
separate function map_partition_varattnos() and call it from appropriate
places. It previously used to be done in get_qual_from_partbound(),
which would lead to wrong results in certain multi-level partitioning
cases, as the mapping would be done for immediate parent-partition pairs.
Now in generate_partition_qual() which is the workhorse of
RelationGetPartitionQual(), we first generate the full expression
(considering all levels of partitioning) and then do the mapping from the
root parent to a leaf partition. It is also possible to generate
partition constraint up to certain non-leaf level and then apply the
same to leaf partitions of that sub-tree after suitable substitution
of varattnos using the new map_partition_varattnos() directly.

Bug fix: ATExecAttachPartition() failed to do the mapping when attaching
a partitioned table as partition. It is possible for the partitions of
such table to have different attributes from the table being attached
and/or the target partitioned table.

Oops, PATCH 2/7 attached with the previous email had a bug in it, whereby
map_partition_varattnos() was not applied to the partition constraint
expressions returned directly from the relcache (rd_partcheck) copy.
Attaching just the updated (only) PATCH 2 which fixes that. Patches
1,3,4,5,6,7/7 from the previous email [1]/messages/by-id/c820c0eb-6935-6f84-8c6a-785fdff130c1@lab.ntt.co.jp are fine. Sorry about that.

Thanks,
Amit

[1]: /messages/by-id/c820c0eb-6935-6f84-8c6a-785fdff130c1@lab.ntt.co.jp
/messages/by-id/c820c0eb-6935-6f84-8c6a-785fdff130c1@lab.ntt.co.jp

Attachments:

0002-Change-how-RelationGetPartitionQual-and-related-code.patchtext/x-diff; name=0002-Change-how-RelationGetPartitionQual-and-related-code.patchDownload
From b10228cfbdefc578138553b29a7658fb78c32de3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 2/7] Change how RelationGetPartitionQual() and related code
 works

Since we always want to recurse, ie, include the parent's partition
constraint (if any), get rid of the argument recurse.

Refactor out the code doing the mapping of attnos of Vars in partition
constraint expressions (parent attnos -> child attnos).  Move it to a
separate function map_partition_varattnos() and call it from appropriate
places.  It previously used to be done in get_qual_from_partbound(),
which would lead to wrong results in certain multi-level partitioning
cases, as the mapping would be done for immediate parent-partition pairs.
Now in generate_partition_qual() which is the workhorse of
RelationGetPartitionQual(), we first generate the full expression
(considering all levels of partitioning) and then do the mapping from the
root parent to a leaf partition.  It is also possible to generate
partition constraint up to certain non-leaf level and then apply the
same to leaf partitions of that sub-tree after suitable substitution
of varattnos using the new map_partition_varattnos() directly.

Bug fix: ATExecAttachPartition() failed to do the mapping when attaching
a partitioned table as partition. It is possible for the partitions of
such table to have different attributes from the table being attached
and/or the target partitioned table.
---
 src/backend/catalog/partition.c      | 97 ++++++++++++++++++++----------------
 src/backend/commands/tablecmds.c     |  9 ++--
 src/backend/executor/execMain.c      |  3 +-
 src/backend/optimizer/util/plancat.c |  2 +-
 src/include/catalog/partition.h      |  3 +-
 5 files changed, 63 insertions(+), 51 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582b77..694cb469e0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,48 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +920,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /* Turn an array of OIDs with N elements into a list */
@@ -1445,7 +1451,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1459,6 +1465,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1466,13 +1476,14 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
@@ -1492,18 +1503,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* the partition's qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..d2e4cfc365 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13323,6 +13323,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13345,8 +13346,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d43a204808..520fe4e0ce 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1256,8 +1256,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+								RelationGetPartitionQual(resultRelationDesc);
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf87b..6ff821e6cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -70,7 +70,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
-- 
2.11.0

#216Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Greg Stark (#214)
Re: Declarative partitioning - another take

On 2016/12/16 17:38, Greg Stark wrote:

Just poking around with partitioning. I notice that "\d parent"
doesn't list all the partitions, suggesting to use \d+ but a plain
"\d" does indeed list the partitions. That seems a bit strange and
also probably impractical if you have hundreds or thousands of
partitions. Has this come up in previous discussions? Unfortunately
it's proving a bit hard to search for "\d" :/

Do you mean a plain "\d" (without an argument) should not list tables that
are partitions? I think that might be preferable. That would mean, we
list only the root partitioned tables with a plain "\d".

Regarding "\d parent", it does the same thing as regular inheritance, but
using the term "partition" instead of "child table". Without specifying a
+ (\d parent), one gets just "Number of partitions: # (Use \d+ to list
them.)" and with + (\d+ parent), one gets the full list of partitions
showing the partition bound with each.

Thanks,
Amit

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

#217Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Dmitry Ivanov (#212)
Re: Declarative partitioning - another take

Hi Dmitry,

On 2016/12/16 0:40, Dmitry Ivanov wrote:

Hi everyone,

Looks like "sql_inheritance" GUC is affecting partitioned tables:

explain (costs off) select * from test;
QUERY PLAN ------------------------------
Append
-> Seq Scan on test
-> Seq Scan on test_1
-> Seq Scan on test_2
-> Seq Scan on test_1_1
-> Seq Scan on test_1_2
-> Seq Scan on test_1_1_1
-> Seq Scan on test_1_2_1
(8 rows)

set sql_inheritance = off;

explain (costs off) select * from test;
QUERY PLAN ------------------
Seq Scan on test
(1 row)

I might be wrong, but IMO this should not happen. Queries involving
update, delete etc on partitioned tables are basically broken. Moreover,
there's no point in performing such operations on a parent table that's
supposed to be empty at all times.

I've come up with a patch which fixes this behavior for UPDATE, DELETE,
TRUNCATE and also in transformTableEntry(). It might be hacky, but it
gives an idea.

I didn't touch RenameConstraint() and renameatt() since this would break
ALTER TABLE ONLY command.

@@ -1198,6 +1198,12 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);

+		/* Use interpretInhOption() unless it's a partitioned table */
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			recurse = interpretInhOption(rv->inhOpt);
+		else
+			recurse = true;
+
 		if (recurse)
 		{
 			ListCell   *child;

If you see the else block of this if, you'll notice this:

else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("must truncate child tables too")));

So that you get this behavior:

# set sql_inheritance to off;
SET

# truncate p;
ERROR: must truncate child tables too

# reset sql_inheritance;
RESET

# truncate only p;
ERROR: must truncate child tables too

# truncate p;
TRUNCATE TABLE

Beside that, I initially had implemented the same thing as what you are
proposing here, but reverted to existing behavior at some point during the
discussion. I think the idea behind was to not *silently* ignore user
specified configuration and instead error out with appropriate message.
While it seems to work reasonably for DDL and maintenance commands (like
TRUNCATE above), things sound strange for SELECT/UPDATE/DELETE as you're
saying.

Thanks,
Amit

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

#218Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#213)
Re: Declarative partitioning - another take

On Fri, Dec 16, 2016 at 3:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Aside from the above, I found few other issues and fixed them in the
attached patches. Descriptions follow:

To avoid any further mistakes on my part, can you please resubmit
these with each patch file containing a proposed commit message
including patch authorship information, who reported the issue, links
to relevant discussion if any, and any other attribution information
which I should not fail to include when committing?

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

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

#219Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#218)
Re: Declarative partitioning - another take

On Sat, Dec 17, 2016 at 1:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Dec 16, 2016 at 3:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Aside from the above, I found few other issues and fixed them in the
attached patches. Descriptions follow:

To avoid any further mistakes on my part, can you please resubmit
these with each patch file containing a proposed commit message
including patch authorship information, who reported the issue, links
to relevant discussion if any, and any other attribution information
which I should not fail to include when committing?

I think it's a good advice and will keep in mind for any patches I
post henceforth.

In this particular case, I found all the issues myself while working
with some more esoteric test scenarios, except the first patch (1/7),
where I have mentioned in the description of the patch in the email,
that there were independent reports of the issue by Tomas Vondra and
David Fetter.

Thanks,
Amit

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

#220Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#219)
7 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/17 11:32, Amit Langote wrote:

On Sat, Dec 17, 2016 at 1:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Dec 16, 2016 at 3:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Aside from the above, I found few other issues and fixed them in the
attached patches. Descriptions follow:

To avoid any further mistakes on my part, can you please resubmit
these with each patch file containing a proposed commit message
including patch authorship information, who reported the issue, links
to relevant discussion if any, and any other attribution information
which I should not fail to include when committing?

I think it's a good advice and will keep in mind for any patches I
post henceforth.

In this particular case, I found all the issues myself while working
with some more esoteric test scenarios, except the first patch (1/7),
where I have mentioned in the description of the patch in the email,
that there were independent reports of the issue by Tomas Vondra and
David Fetter.

Here are updated patches including the additional information.

Thanks,
Amit

Attachments:

0001-Invalidate-the-parent-s-relcache-after-partition-cre-2.patchtext/x-diff; name=0001-Invalidate-the-parent-s-relcache-after-partition-cre-2.patchDownload
From c2afac3447a8bd2f7f9004e8b7e258039ca2296b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:06 +0900
Subject: [PATCH 1/7] Invalidate the parent's relcache after partition
 creation.

CREATE TABLE PARTITION OF failed to invalidate the parent table's
relcache, causing the subsequent commands in the same transaction to
not see the new partition.

Reported by: Tomas Vondra and David Fetter
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/22dd313b-d7fd-22b5-0787-654845c8f849%402ndquadrant.com
         https://www.postgresql.org/message-id/20161215090916.GB20659%40fetter.org
---
 src/backend/catalog/heap.c       |  7 ++++++-
 src/backend/commands/tablecmds.c | 13 ++++---------
 src/include/catalog/heap.h       |  2 +-
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c09c9f28a7..e5d6aecc3f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3230,9 +3230,12 @@ RemovePartitionKeyByRelId(Oid relid)
  * StorePartitionBound
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
+ *
+ * Also, invalidate the parent's relcache, so that the next rebuild will load
+ * the new partition's info into its partition descriptor.
  */
 void
-StorePartitionBound(Relation rel, Node *bound)
+StorePartitionBound(Relation rel, Relation parent, Node *bound)
 {
 	Relation	classRel;
 	HeapTuple	tuple,
@@ -3273,4 +3276,6 @@ StorePartitionBound(Relation rel, Node *bound)
 	CatalogUpdateIndexes(classRel, newtuple);
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
+
+	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a574dc50d..1c219b03dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -777,10 +777,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * it does not return on error.
 		 */
 		check_new_partition_bound(relname, parent, bound);
-		heap_close(parent, NoLock);
 
 		/* Update the pg_class entry. */
-		StorePartitionBound(rel, bound);
+		StorePartitionBound(rel, parent, bound);
+
+		heap_close(parent, NoLock);
 
 		/*
 		 * The code that follows may also update the pg_class tuple to update
@@ -13141,7 +13142,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 							  cmd->bound);
 
 	/* Update the pg_class entry. */
-	StorePartitionBound(attachRel, cmd->bound);
+	StorePartitionBound(attachRel, rel, cmd->bound);
 
 	/*
 	 * Generate partition constraint from the partition bound specification.
@@ -13352,12 +13353,6 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		}
 	}
 
-	/*
-	 * Invalidate the parent's relcache so that the new partition is now
-	 * included its partition descriptor.
-	 */
-	CacheInvalidateRelcache(rel);
-
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
 	/* keep our lock until commit */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 77dc1983e8..0e4262f611 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,6 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
-extern void StorePartitionBound(Relation rel, Node *bound);
+extern void StorePartitionBound(Relation rel, Relation parent, Node *bound);
 
 #endif   /* HEAP_H */
-- 
2.11.0

0002-Change-how-RelationGetPartitionQual-and-related-code-2.patchtext/x-diff; name=0002-Change-how-RelationGetPartitionQual-and-related-code-2.patchDownload
From 21d38e05d1107a88178e9954b1f4a3804cd9e154 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 2/7] Change how RelationGetPartitionQual() and related code
 works

Since we always want to recurse, ie, include the parent's partition
constraint (if any), get rid of the argument recurse.

Refactor out the code doing the mapping of attnos of Vars in partition
constraint expressions (parent attnos -> child attnos).  Move it to a
separate function map_partition_varattnos() and call it from appropriate
places.  It previously used to be done in get_qual_from_partbound(),
which would lead to wrong results in certain multi-level partitioning
cases, as the mapping would be done for immediate parent-partition pairs.
Now in generate_partition_qual() which is the workhorse of
RelationGetPartitionQual(), we first generate the full expression
(considering all levels of partitioning) and then do the mapping from the
root parent to a leaf partition.

It is also possible to generate partition constraint up to certain non-leaf
level and then apply the same to leaf partitions of that sub-tree after
suitable substitution of varattnos using the new map_partition_varattnos()
directly.  This is what happens some tuple-routing cases, e.g., if the main
target table is some non-root partitioned table in a partition tree. In
such cases, we need to apply the partition constraint of the target table
to the leaf partition into which a given tuple is routed.

Bug fix: ATExecAttachPartition() failed to do the mapping when attaching
a partitioned table as partition. Since it is possible for the partitions
of such table to have different attributes from the table being attached
and/or the target partitioned table, the result of applying the partition
constraint during the validation scan could be incorrect.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c      | 97 ++++++++++++++++++++----------------
 src/backend/commands/tablecmds.c     |  9 ++--
 src/backend/executor/execMain.c      |  3 +-
 src/backend/optimizer/util/plancat.c |  2 +-
 src/include/catalog/partition.h      |  3 +-
 5 files changed, 63 insertions(+), 51 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582b77..694cb469e0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,48 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +920,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /* Turn an array of OIDs with N elements into a list */
@@ -1445,7 +1451,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1459,6 +1465,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1466,13 +1476,14 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
@@ -1492,18 +1503,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* the partition's qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..d2e4cfc365 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13323,6 +13323,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13345,8 +13346,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d43a204808..520fe4e0ce 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1256,8 +1256,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+								RelationGetPartitionQual(resultRelationDesc);
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf87b..6ff821e6cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -70,7 +70,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
-- 
2.11.0

0003-Refactor-tuple-routing-setup-code-2.patchtext/x-diff; name=0003-Refactor-tuple-routing-setup-code-2.patchDownload
From fd44997465c1f19d4213126355d9638442cbb38b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 15:56:27 +0900
Subject: [PATCH 3/7] Refactor tuple-routing setup code

It's unnecessarily repeated in copy.c and nodeModifyTable.c, which makes
it a burden to maintain.  Should've been like this to begin with.

I moved the common code to ExecSetupPartitionTupleRouting() in execMain.c
that also houses ExecFindParttion() currently.  Hmm, should there be a
new src/backend/executor/execPartition.c?

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 72 ++++++----------------------
 src/backend/executor/execMain.c        | 85 ++++++++++++++++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c | 76 ++++++------------------------
 src/include/executor/executor.h        |  5 ++
 4 files changed, 120 insertions(+), 118 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a8da338f0..d5901651db 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1406,64 +1406,22 @@ BeginCopy(ParseState *pstate,
 		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			List	   *leaf_parts;
-			ListCell   *cell;
-			int			i,
-						num_parted;
-			ResultRelInfo *leaf_part_rri;
-
-			/* Get the tuple-routing information and lock partitions */
-			cstate->partition_dispatch_info =
-				RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-												 &num_parted,
-												 &leaf_parts);
+			PartitionDispatch  *partition_dispatch_info;
+			ResultRelInfo	   *partitions;
+			TupleConversionMap **partition_tupconv_maps;
+			int					num_parted,
+								num_partitions;
+
+			ExecSetupPartitionTupleRouting(rel,
+										   &partition_dispatch_info,
+										   &partitions,
+										   &partition_tupconv_maps,
+										   &num_parted, &num_partitions);
+			cstate->partition_dispatch_info = partition_dispatch_info;
 			cstate->num_dispatch = num_parted;
-			cstate->num_partitions = list_length(leaf_parts);
-			cstate->partitions = (ResultRelInfo *)
-				palloc(cstate->num_partitions *
-					   sizeof(ResultRelInfo));
-			cstate->partition_tupconv_maps = (TupleConversionMap **)
-				palloc0(cstate->num_partitions *
-						sizeof(TupleConversionMap *));
-
-			leaf_part_rri = cstate->partitions;
-			i = 0;
-			foreach(cell, leaf_parts)
-			{
-				Relation	partrel;
-
-				/*
-				 * We locked all the partitions above including the leaf
-				 * partitions.  Note that each of the relations in
-				 * cstate->partitions will be closed by CopyFrom() after it's
-				 * finished with its processing.
-				 */
-				partrel = heap_open(lfirst_oid(cell), NoLock);
-
-				/*
-				 * Verify result relation is a valid target for the current
-				 * operation.
-				 */
-				CheckValidResultRel(partrel, CMD_INSERT);
-
-				InitResultRelInfo(leaf_part_rri,
-								  partrel,
-								  1,	/* dummy */
-								  false,		/* no partition constraint
-												 * check */
-								  0);
-
-				/* Open partition indices */
-				ExecOpenIndices(leaf_part_rri, false);
-
-				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
-					cstate->partition_tupconv_maps[i] =
-						convert_tuples_by_name(tupDesc,
-											   RelationGetDescr(partrel),
-								 gettext_noop("could not convert row type"));
-				leaf_part_rri++;
-				i++;
-			}
+			cstate->partitions = partitions;
+			cstate->num_partitions = num_partitions;
+			cstate->partition_tupconv_maps = partition_tupconv_maps;
 		}
 	}
 	else
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 520fe4e0ce..b3cedadce4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "tcop/utility.h"
@@ -2998,6 +2999,90 @@ EvalPlanQualEnd(EPQState *epqstate)
 }
 
 /*
+ * ExecSetupPartitionTupleRouting - set up information needed during
+ * tuple routing for partitioned tables
+ *
+ * Output arguments:
+ * 'pd' receives an array of PartitionDispatch objects with one entry for
+ *		every partitioned table in the partition tree
+ * 'partitions' receives an array of ResultRelInfo objects with one entry for
+ *		every leaf partition in the partition tree
+ * 'tup_conv_maps' receives an array of TupleConversionMap objects with one
+ *		entry for every leaf partition (required to convert input tuple based
+ *		on the root table's rowtype to a leaf partition's rowtype after tuple
+ *		routing is done
+ * 'num_parted' receives the number of partitioned tables in the partition
+ *		tree (= the number of entries in the 'pd' output array)
+ * 'num_partitions' receives the number of leaf partitions in the partition
+ *		tree (= the number of entries in the 'partitions' and 'tup_conv_maps'
+ *		output arrays
+ *
+ * Note that all the relations in the partition tree are locked using the
+ * RowExclusiveLock mode upon return from this function.
+ */
+void
+ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions)
+{
+	TupleDesc	tupDesc = RelationGetDescr(rel);
+	List	   *leaf_parts;
+	ListCell   *cell;
+	int			i;
+	ResultRelInfo *leaf_part_rri;
+
+	/* Get the tuple-routing information and lock partitions */
+	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
+										   &leaf_parts);
+	*num_partitions = list_length(leaf_parts);
+	*partitions = (ResultRelInfo *) palloc(*num_partitions *
+										   sizeof(ResultRelInfo));
+	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
+										   sizeof(TupleConversionMap *));
+
+	leaf_part_rri = *partitions;
+	i = 0;
+	foreach(cell, leaf_parts)
+	{
+		Relation	partrel;
+		TupleDesc	part_tupdesc;
+
+		/*
+		 * We locked all the partitions above including the leaf partitions.
+		 * Note that each of the relations in *partitions are eventually
+		 * closed by the caller.
+		 */
+		partrel = heap_open(lfirst_oid(cell), NoLock);
+		part_tupdesc = RelationGetDescr(partrel);
+
+		/*
+		 * Verify result relation is a valid target for the current operation.
+		 */
+		CheckValidResultRel(partrel, CMD_INSERT);
+
+		/*
+		 * Save a tuple conversion map to convert a tuple routed to this
+		 * partition from the parent's type to the partition's.
+		 */
+		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+								 gettext_noop("could not convert row type"));
+
+		InitResultRelInfo(leaf_part_rri,
+						  partrel,
+						  1,	 /* dummy */
+						  false,
+						  0);
+
+		/* Open partition indices */
+		ExecOpenIndices(leaf_part_rri, false);
+		leaf_part_rri++;
+		i++;
+	}
+}
+
+/*
  * ExecFindPartition -- Find a leaf partition in the partition tree rooted
  * at parent, for the heap tuple contained in *slot
  *
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ec440b353d..a9546106ce 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1718,68 +1718,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (operation == CMD_INSERT &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		int					i,
-							j,
-							num_parted;
-		List			   *leaf_parts;
-		ListCell		   *cell;
-		ResultRelInfo	   *leaf_part_rri;
-
-		/* Get the tuple-routing information and lock partitions */
-		mtstate->mt_partition_dispatch_info =
-					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-													 &num_parted,
-													 &leaf_parts);
+		PartitionDispatch  *partition_dispatch_info;
+		ResultRelInfo	   *partitions;
+		TupleConversionMap **partition_tupconv_maps;
+		int					num_parted,
+							num_partitions;
+
+		ExecSetupPartitionTupleRouting(rel,
+									   &partition_dispatch_info,
+									   &partitions,
+									   &partition_tupconv_maps,
+									   &num_parted, &num_partitions);
+		mtstate->mt_partition_dispatch_info = partition_dispatch_info;
 		mtstate->mt_num_dispatch = num_parted;
-		mtstate->mt_num_partitions = list_length(leaf_parts);
-		mtstate->mt_partitions = (ResultRelInfo *)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(ResultRelInfo));
-		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(TupleConversionMap *));
-
-		leaf_part_rri = mtstate->mt_partitions;
-		i = j = 0;
-		foreach(cell, leaf_parts)
-		{
-			Oid			partrelid = lfirst_oid(cell);
-			Relation	partrel;
-
-			/*
-			 * We locked all the partitions above including the leaf
-			 * partitions.  Note that each of the relations in
-			 * mtstate->mt_partitions will be closed by ExecEndModifyTable().
-			 */
-			partrel = heap_open(partrelid, NoLock);
-
-			/*
-			 * Verify result relation is a valid target for the current
-			 * operation
-			 */
-			CheckValidResultRel(partrel, CMD_INSERT);
-
-			InitResultRelInfo(leaf_part_rri,
-							  partrel,
-							  1,		/* dummy */
-							  false,	/* no partition constraint checks */
-							  eflags);
-
-			/* Open partition indices (note: ON CONFLICT unsupported)*/
-			if (partrel->rd_rel->relhasindex && operation != CMD_DELETE &&
-				leaf_part_rri->ri_IndexRelationDescs == NULL)
-				ExecOpenIndices(leaf_part_rri, false);
-
-			if (!equalTupleDescs(RelationGetDescr(rel),
-								 RelationGetDescr(partrel)))
-				mtstate->mt_partition_tupconv_maps[i] =
-							convert_tuples_by_name(RelationGetDescr(rel),
-												   RelationGetDescr(partrel),
-								  gettext_noop("could not convert row type"));
-
-			leaf_part_rri++;
-			i++;
-		}
+		mtstate->mt_partitions = partitions;
+		mtstate->mt_num_partitions = num_partitions;
+		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
 	}
 
 	/*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3f649faf2f..b74fa5eb5d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -213,6 +213,11 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern void ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions);
 extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
 				  TupleTableSlot *slot,
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition-2.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition-2.patchDownload
From 8b88ecbb0805d54aa4f69dd0b7a3ab6cdc31c8ed Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

InitResultRelInfo()'s API changes with this.  Instead of passing
a boolean telling whether or not to load the partition constraint,
callers now need to pass the exact constraint expression to use
as ri_PartitionCheck or NIL.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  7 +++++-
 src/backend/commands/tablecmds.c     |  2 +-
 src/backend/executor/execMain.c      | 42 ++++++++++++++++++++++++++++++------
 src/include/executor/executor.h      |  3 +--
 src/test/regress/expected/insert.out |  4 ++++
 src/test/regress/sql/insert.sql      |  3 +++
 6 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d5901651db..a0eb4241e2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2294,6 +2294,7 @@ CopyFrom(CopyState cstate)
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
+	List	   *partcheck = NIL;
 
 #define MAX_BUFFERED_TUPLES 1000
 	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
@@ -2410,6 +2411,10 @@ CopyFrom(CopyState cstate)
 		hi_options |= HEAP_INSERT_FROZEN;
 	}
 
+	/* Don't forget the partition constraints */
+	if (cstate->rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(cstate->rel);
+
 	/*
 	 * We need a ResultRelInfo so we can use the regular executor's
 	 * index-entry-making machinery.  (There used to be a huge amount of code
@@ -2419,7 +2424,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
+					  partcheck,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2e4cfc365..4bd4ec4e8c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
+						  NIL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b3cedadce4..1b19bc38ef 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -821,13 +821,19 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			Index		resultRelationIndex = lfirst_int(l);
 			Oid			resultRelationOid;
 			Relation	resultRelation;
+			List	   *partcheck = NIL;
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
+			/* Don't forget the partition constraint */
+			if (resultRelation->rd_rel->relispartition)
+				partcheck = RelationGetPartitionQual(resultRelation);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
+							  partcheck,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1217,7 +1223,7 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1255,9 +1261,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-								RelationGetPartitionQual(resultRelationDesc);
+	resultRelInfo->ri_PartitionCheck = partition_check;
 }
 
 /*
@@ -1284,6 +1288,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	ListCell   *l;
 	Relation	rel;
 	MemoryContext oldcontext;
+	List	   *partcheck = NIL;
 
 	/* First, search through the query result relations */
 	rInfo = estate->es_result_relations;
@@ -1312,6 +1317,10 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	 */
 	rel = heap_open(relid, NoLock);
 
+	/* Don't forget the partition constraint */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	/*
 	 * Make the new entry in the right context.
 	 */
@@ -1320,7 +1329,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
+					  partcheck,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -3032,6 +3041,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	ListCell   *cell;
 	int			i;
 	ResultRelInfo *leaf_part_rri;
+	List		  *partcheck = NIL;
 
 	/* Get the tuple-routing information and lock partitions */
 	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
@@ -3042,12 +3052,22 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
 										   sizeof(TupleConversionMap *));
 
+	/*
+	 * If the main target rel is a partition, ExecConstraints() as applied to
+	 * each leaf partition must consider its partition constraint, because
+	 * unlike explicit constraints, an implicit partition constraint is not
+	 * inherited.
+	 */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	leaf_part_rri = *partitions;
 	i = 0;
 	foreach(cell, leaf_parts)
 	{
 		Relation	partrel;
 		TupleDesc	part_tupdesc;
+		List	   *my_check = NIL;
 
 		/*
 		 * We locked all the partitions above including the leaf partitions.
@@ -3069,10 +3089,18 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
 								 gettext_noop("could not convert row type"));
 
+		/*
+		 * This is the parent's partition constraint, so any Vars in
+		 * it bear the its attribute numbers.  We must switch them to
+		 * the leaf partition's.
+		 */
+		if (partcheck)
+			my_check = map_partition_varattnos(partcheck, partrel, rel);
+
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
+						  my_check,
 						  0);
 
 		/* Open partition indices */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5eb5d..c8e42ae2eb 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -73,7 +73,6 @@
 #define ExecEvalExpr(expr, econtext, isNull, isDone) \
 	((*(expr)->evalfunc) (expr, econtext, isNull, isDone))
 
-
 /* Hook for plugins to get control in ExecutorStart() */
 typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
 extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
@@ -189,7 +188,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 561cefa3c4..95a7c4da7a 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,10 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- fail due to partition constraint failure
+insert into part_ee_ff values ('gg', 1);
+ERROR:  new row for relation "part_ee_ff1" violates partition constraint
+DETAIL:  Failing row contains (gg, 1).
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 846bb5897a..77577682ac 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,9 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- fail due to partition constraint failure
+insert into part_ee_ff values ('gg', 1);
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0005-Fix-oddities-of-tuple-routing-and-TupleTableSlots-2.patchtext/x-diff; name=0005-Fix-oddities-of-tuple-routing-and-TupleTableSlots-2.patchDownload
From f012daa2a2380582b5d56dfcb7810b363665846c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 17:39:19 +0900
Subject: [PATCH 5/7] Fix oddities of tuple-routing and TupleTableSlots

We must use the partition's tuple descriptor *after* a tuple is routed,
not the root table's.  Partition's attributes, for example, may be
ordered diferently from the root table's.

We must then switch back to the root table's for the next tuple, because
computing partition key of a tuple to be routed must be looking at the
root table's tuple descriptor.  A dedicated TupleTableSlot is allocated
within EState called es_partition_tuple_slot whose descriptor is set to
a given leaf partition for every input tuple after it's routed.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 28 +++++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c | 25 +++++++++++++++++++++++++
 src/include/nodes/execnodes.h          |  3 +++
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a0eb4241e2..bec8c73903 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2441,6 +2441,15 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
+	 * Initialize a dedicated slot to manipulate tuples of any given
+	 * partition's rowtype.
+	 */
+	if (cstate->partition_dispatch_info)
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+	else
+		estate->es_partition_tuple_slot = NULL;
+
+	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2489,7 +2498,8 @@ CopyFrom(CopyState cstate)
 
 	for (;;)
 	{
-		TupleTableSlot *slot;
+		TupleTableSlot *slot,
+					   *oldslot = NULL;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2576,7 +2586,19 @@ CopyFrom(CopyState cstate)
 			map = cstate->partition_tupconv_maps[leaf_part_index];
 			if (map)
 			{
+				Relation	partrel = resultRelInfo->ri_RelationDesc;
+
 				tuple = do_convert_tuple(tuple, map);
+
+				/*
+				 * We must use the partition's tuple descriptor from this
+				 * point on.  Use a dedicated slot from this point on until
+				 * we're finished dealing with the partition.
+				 */
+				oldslot = slot;
+				slot = estate->es_partition_tuple_slot;
+				Assert(slot != NULL);
+				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 			}
 
@@ -2672,6 +2694,10 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
+
+				/* Switch back to the slot corresponding to the root table */
+				Assert(oldslot != NULL);
+				slot = oldslot;
 			}
 		}
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a9546106ce..da4c96a863 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,6 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	TupleTableSlot *oldslot = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -318,7 +319,19 @@ ExecInsert(ModifyTableState *mtstate,
 		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
 		if (map)
 		{
+			Relation partrel = resultRelInfo->ri_RelationDesc;
+
 			tuple = do_convert_tuple(tuple, map);
+
+			/*
+			 * We must use the partition's tuple descriptor from this
+			 * point on, until we're finished dealing with the partition.
+			 * Use the dedicated slot for that.
+			 */
+			oldslot = slot;
+			slot = estate->es_partition_tuple_slot;
+			Assert(slot != NULL);
+			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 		}
 	}
@@ -566,6 +579,10 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
+
+		/* Switch back to the slot corresponding to the root table */
+		Assert(oldslot != NULL);
+		slot = oldslot;
 	}
 
 	/*
@@ -1734,7 +1751,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
+
+		/*
+		 * Initialize a dedicated slot to manipulate tuples of any given
+		 * partition's rowtype.
+		 */
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
 	}
+	else
+		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 703604ab9d..f49702b122 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -384,6 +384,9 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
+	/* Slot used to manipulate a tuple after it is routed to a partition */
+	TupleTableSlot *es_partition_tuple_slot;
+
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
-- 
2.11.0

0006-Make-ExecConstraints-emit-the-correct-row-in-error-m-2.patchtext/x-diff; name=0006-Make-ExecConstraints-emit-the-correct-row-in-error-m-2.patchDownload
From ef83ef4cc90a43acf22c097480f71b2b3c1c30f1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 18:00:47 +0900
Subject: [PATCH 6/7] Make ExecConstraints() emit the correct row in error msgs

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root
table's rowtype and the partition's.

To convert back to the correct row format, keep root table relation
descriptor and a reverse tuple conversion map in the ResultRelInfo's
of leaf partitions.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  2 +-
 src/backend/commands/tablecmds.c     |  2 +-
 src/backend/executor/execMain.c      | 83 ++++++++++++++++++++++++++++++++----
 src/include/executor/executor.h      |  2 +
 src/include/nodes/execnodes.h        |  2 +
 src/test/regress/expected/insert.out | 15 ++++++-
 src/test/regress/sql/insert.sql      | 11 +++++
 7 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index bec8c73903..9cd84e80c7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2424,7 +2424,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  partcheck,
+					  partcheck, NULL, NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4bd4ec4e8c..67ff1715ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  NIL,
+						  NIL, NULL, NULL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1b19bc38ef..b56fd8ca6f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -833,7 +833,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  partcheck,
+							  partcheck, NULL, NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1224,6 +1224,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1262,6 +1264,12 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
 	resultRelInfo->ri_PartitionCheck = partition_check;
+	/*
+	 * Following fields are only looked at in some tuple-routing cases.
+	 * In other case, they are set to NULL.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
+	resultRelInfo->ri_PartitionReverseMap = partition_reverse_map;
 }
 
 /*
@@ -1329,7 +1337,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  partcheck,
+					  partcheck, NULL, NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1775,6 +1783,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * In case where the tuple is routed, it's been converted
+				 * to the partition's rowtype, which might differ from the
+				 * root table's.  We must convert it back to the root table's
+				 * type so that it's shown correctly in the error message.
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+					Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+					tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1788,9 +1816,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1802,6 +1830,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+				tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1814,9 +1856,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1824,6 +1866,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+			Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+			tuple = do_convert_tuple(tuple,
+									 resultRelInfo->ri_PartitionReverseMap);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1836,7 +1892,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3068,6 +3124,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		Relation	partrel;
 		TupleDesc	part_tupdesc;
 		List	   *my_check = NIL;
+		TupleConversionMap	*reverse_map;
 
 		/*
 		 * We locked all the partitions above including the leaf partitions.
@@ -3097,10 +3154,20 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		if (partcheck)
 			my_check = map_partition_varattnos(partcheck, partrel, rel);
 
+		/*
+		 * We must save a reverse tuple conversion map as well, to show the
+		 * correct input tuple in the error message shown by ExecConstraints()
+		 * in case of routed tuples.  Remember that at the point of failure,
+		 * the tuple has been converted to the partition's type which might
+		 * not match the input tuple.
+		 */
+		reverse_map = convert_tuples_by_name(part_tupdesc, tupDesc,
+								 gettext_noop("could not convert row type"));
+
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  my_check,
+						  my_check, rel, reverse_map,
 						  0);
 
 		/* Open partition indices */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c8e42ae2eb..3f91d5734e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,6 +189,8 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f49702b122..6d06af949a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,8 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
+	TupleConversionMap *ri_PartitionReverseMap;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 95a7c4da7a..328f776dd7 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -289,6 +289,18 @@ select tableoid::regclass, * from list_parted;
 insert into part_ee_ff values ('gg', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (gg, 1).
+-- check that correct row is shown in the error message if constraint fails
+-- after a tuple is routed to a partition with different rowtype from the
+-- root table
+create table part_ee_ff3 (like part_ee_ff);
+alter table part_ee_ff3 drop a;
+alter table part_ee_ff3 add a text; -- (a's attnum is now 3)
+alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30);
+truncate part_ee_ff;
+alter table part_ee_ff add constraint check_b_25 check (b = 25);
+insert into list_parted values ('ee', 20);
+ERROR:  new row for relation "part_ee_ff3" violates check constraint "check_b_25"
+DETAIL:  Failing row contains (ee, 20).
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -297,10 +309,11 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 7 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 77577682ac..ebe371e884 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -170,6 +170,17 @@ select tableoid::regclass, * from list_parted;
 -- fail due to partition constraint failure
 insert into part_ee_ff values ('gg', 1);
 
+-- check that correct row is shown in the error message if constraint fails
+-- after a tuple is routed to a partition with different rowtype from the
+-- root table
+create table part_ee_ff3 (like part_ee_ff);
+alter table part_ee_ff3 drop a;
+alter table part_ee_ff3 add a text; -- (a's attnum is now 3)
+alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30);
+truncate part_ee_ff;
+alter table part_ee_ff add constraint check_b_25 check (b = 25);
+insert into list_parted values ('ee', 20);
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0007-Add-some-more-tests-for-tuple-routing-2.patchtext/x-diff; name=0007-Add-some-more-tests-for-tuple-routing-2.patchDownload
From 71590d20c1f8bbe8fb1f4996ff343066a21ea256 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 16 Dec 2016 09:45:57 +0900
Subject: [PATCH 7/7] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 40 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 19 +++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 328f776dd7..aaa74da607 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -301,6 +301,36 @@ alter table part_ee_ff add constraint check_b_25 check (b = 25);
 insert into list_parted values ('ee', 20);
 ERROR:  new row for relation "part_ee_ff3" violates check constraint "check_b_25"
 DETAIL:  Failing row contains (ee, 20).
+alter table part_ee_ff drop constraint check_b_25;
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff4 partition of part_ee_ff for values from (30) to (40) partition by range (b);
+create table part_ee_ff4_1 partition of part_ee_ff4 for values from (30) to (35);
+create table part_ee_ff4_2 partition of part_ee_ff4 for values from (35) to (40);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 39) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3   | Ff |    20 |    29
+ part_ee_ff4_1 | Ff |    30 |    34
+ part_ee_ff4_2 | Ff |    35 |    39
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(10 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -309,7 +339,7 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 7 other objects
+NOTICE:  drop cascades to 15 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
@@ -317,3 +347,11 @@ drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
 drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff4
+drop cascades to table part_ee_ff4_1
+drop cascades to table part_ee_ff4_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index ebe371e884..0fa9cef4b5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -180,6 +180,25 @@ alter table part_ee_ff attach partition part_ee_ff3 for values from (20) to (30)
 truncate part_ee_ff;
 alter table part_ee_ff add constraint check_b_25 check (b = 25);
 insert into list_parted values ('ee', 20);
+alter table part_ee_ff drop constraint check_b_25;
+
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff4 partition of part_ee_ff for values from (30) to (40) partition by range (b);
+create table part_ee_ff4_1 partition of part_ee_ff4 for values from (30) to (35);
+create table part_ee_ff4_2 partition of part_ee_ff4 for values from (35) to (40);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 39) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
 
 -- cleanup
 drop table range_parted cascade;
-- 
2.11.0

#221Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#220)
Re: Declarative partitioning - another take

On Sun, Dec 18, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Here are updated patches including the additional information.

Thanks. Committed 0001. Will have to review the others when I'm less tired.

BTW, elog() is only supposed to be used for can't happen error
messages; when it is used, no translation is done. So this is wrong:

if (skip_validate)
elog(NOTICE, "skipping scan to validate partition constraint");

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

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

#222Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#221)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/20 12:59, Robert Haas wrote:

On Sun, Dec 18, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Here are updated patches including the additional information.

Thanks. Committed 0001. Will have to review the others when I'm less tired.

Thanks!

BTW, elog() is only supposed to be used for can't happen error
messages; when it is used, no translation is done. So this is wrong:

if (skip_validate)
elog(NOTICE, "skipping scan to validate partition constraint");

You're right. I was using it for debugging when I first wrote that code,
but then thought it would be better to eventually turn that into
ereport(INFO/NOTICE) for the final submission, which I missed to do. Sorry
about that. Here's a patch.

Thanks,
Amit

Attachments:

turn-elog-into-ereport-1.patchtext/x-diff; name=turn-elog-into-ereport-1.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..6a179596ce 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13297,8 +13297,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		}
 	}
 
+	/* It's safe to skip the validation scan after all */
 	if (skip_validate)
-		elog(NOTICE, "skipping scan to validate partition constraint");
+		ereport(INFO,
+				(errmsg("skipping scan to validate partition constraint")));
 
 	/*
 	 * Set up to have the table to be scanned to validate the partition
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 99e20eb922..2a324c0b49 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3179,7 +3179,7 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  skipping scan to validate partition constraint
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3204,7 +3204,7 @@ CREATE TABLE part2 (
 	b int NOT NULL CHECK (b >= 10 AND b < 18)
 );
 ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  skipping scan to validate partition constraint
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3219,7 +3219,7 @@ ERROR:  partition constraint is violated by some row
 DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  skipping scan to validate partition constraint
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
#223Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Amit Langote (#222)
Re: Declarative partitioning - another take

Amit Langote wrote:

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..6a179596ce 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13297,8 +13297,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
}
}
+	/* It's safe to skip the validation scan after all */
if (skip_validate)
-		elog(NOTICE, "skipping scan to validate partition constraint");
+		ereport(INFO,
+				(errmsg("skipping scan to validate partition constraint")));

Why not just remove the message altogether?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#224Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#223)
Re: Declarative partitioning - another take

On Tue, Dec 20, 2016 at 4:51 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Amit Langote wrote:

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..6a179596ce 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13297,8 +13297,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
}
}
+     /* It's safe to skip the validation scan after all */
if (skip_validate)
-             elog(NOTICE, "skipping scan to validate partition constraint");
+             ereport(INFO,
+                             (errmsg("skipping scan to validate partition constraint")));

Why not just remove the message altogether?

That's certainly an option. It might be noise in some situations. On
the other hand, it affects whether attaching the partition is O(1) or
O(n), so somebody might well want to know. Or maybe they might be
more likely to want a message in the reverse situation, telling them
that the partition constraint DOES need to be validated. I'm not sure
what the best user interface is here; thoughts welcome.

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

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

#225Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#224)
Re: Declarative partitioning - another take

Robert Haas wrote:

On Tue, Dec 20, 2016 at 4:51 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Amit Langote wrote:

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..6a179596ce 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13297,8 +13297,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
}
}
+     /* It's safe to skip the validation scan after all */
if (skip_validate)
-             elog(NOTICE, "skipping scan to validate partition constraint");
+             ereport(INFO,
+                             (errmsg("skipping scan to validate partition constraint")));

Why not just remove the message altogether?

That's certainly an option. It might be noise in some situations. On
the other hand, it affects whether attaching the partition is O(1) or
O(n), so somebody might well want to know. Or maybe they might be
more likely to want a message in the reverse situation, telling them
that the partition constraint DOES need to be validated. I'm not sure
what the best user interface is here; thoughts welcome.

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

(We have the table_rewrite event trigger, for a very similar use case.)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#226Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#225)
Re: Declarative partitioning - another take

On Tue, Dec 20, 2016 at 10:27 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

Maybe something like: partition constraint for table \"%s\" is implied
by existing constraints

(We have the table_rewrite event trigger, for a very similar use case.)

Hmm, maybe we should see if we can safely support an event trigger here, too.

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

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

#227Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#226)
Re: Declarative partitioning - another take

Robert Haas wrote:

On Tue, Dec 20, 2016 at 10:27 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

Maybe something like: partition constraint for table \"%s\" is implied
by existing constraints

Actually, shouldn't we emit a message if we *don't* skip the check?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#228Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#221)
Re: Declarative partitioning - another take

On Mon, Dec 19, 2016 at 10:59 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Dec 18, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Here are updated patches including the additional information.

Thanks. Committed 0001. Will have to review the others when I'm less tired.

0002. Can you add a test case for the bug fixed by this patch?

0003. Loses equalTupleDescs() check and various cases where
ExecOpenIndexes can be skipped. Isn't that bad? Also, "We locked all
the partitions above including the leaf partitions" should say "Caller
must have locked all the partitions including the leaf partitions".

0004. Unnecessary whitespace change in executor.h. Still don't
understand why we need to hoist RelationGetPartitionQual() into the
caller.

0005. Can you add a test case for the bug fixed by this patch?

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

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

#229Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Alvaro Herrera (#227)
Re: Declarative partitioning - another take

On 2016/12/21 1:45, Alvaro Herrera wrote:

Robert Haas wrote:

On Tue, Dec 20, 2016 at 10:27 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

Maybe something like: partition constraint for table \"%s\" is implied
by existing constraints

Actually, shouldn't we emit a message if we *don't* skip the check?

Scanning (aka, not skipping) to validate the partition constraint is the
default behavior, so a user would be expecting it anyway, IOW, need not be
informed of it. But when ATExecAttachPartition's efforts to avoid the
scan by comparing the partition constraint against existing constraints
(which the user most probably deliberately added just for this) succeed,
that seems like a better piece of information to provide the user with,
IMHO. But then again, having a message printed before a potentially long
validation scan seems like something a user would like to see, to know
what it is that is going to take so long. Hmm.

Anyway, what would the opposite of Robert's suggested message look like:
"scanning table \"%s\" to validate partition constraint"?

Thanks,
Amit

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

#230Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#229)
Re: Declarative partitioning - another take

On Tue, Dec 20, 2016 at 9:14 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/21 1:45, Alvaro Herrera wrote:

Robert Haas wrote:

On Tue, Dec 20, 2016 at 10:27 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

Maybe something like: partition constraint for table \"%s\" is implied
by existing constraints

Actually, shouldn't we emit a message if we *don't* skip the check?

Scanning (aka, not skipping) to validate the partition constraint is the
default behavior, so a user would be expecting it anyway, IOW, need not be
informed of it. But when ATExecAttachPartition's efforts to avoid the
scan by comparing the partition constraint against existing constraints
(which the user most probably deliberately added just for this) succeed,
that seems like a better piece of information to provide the user with,
IMHO. But then again, having a message printed before a potentially long
validation scan seems like something a user would like to see, to know
what it is that is going to take so long. Hmm.

Anyway, what would the opposite of Robert's suggested message look like:
"scanning table \"%s\" to validate partition constraint"?

Maybe: partition constraint for table \"%s\" is implied by existing constraints

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

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

#231Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#230)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/21 13:42, Robert Haas wrote:

On Tue, Dec 20, 2016 at 9:14 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/21 1:45, Alvaro Herrera wrote:

Robert Haas wrote:

On Tue, Dec 20, 2016 at 10:27 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Even if we decide to keep the message, I think it's not very good
wording anyhow; as a translator I disliked it on sight. Instead of
"skipping scan to validate" I would use "skipping validation scan",
except that it's not clear what it is we're validating. Mentioning
partition constraint in errcontext() doesn't like a great solution, but
I can't think of anything better.

Maybe something like: partition constraint for table \"%s\" is implied
by existing constraints

Actually, shouldn't we emit a message if we *don't* skip the check?

Scanning (aka, not skipping) to validate the partition constraint is the
default behavior, so a user would be expecting it anyway, IOW, need not be
informed of it. But when ATExecAttachPartition's efforts to avoid the
scan by comparing the partition constraint against existing constraints
(which the user most probably deliberately added just for this) succeed,
that seems like a better piece of information to provide the user with,
IMHO. But then again, having a message printed before a potentially long
validation scan seems like something a user would like to see, to know
what it is that is going to take so long. Hmm.

Anyway, what would the opposite of Robert's suggested message look like:
"scanning table \"%s\" to validate partition constraint"?

Maybe: partition constraint for table \"%s\" is implied by existing constraints

OK, updated patch attached.

Thanks,
Amit

Attachments:

turn-elog-into-ereport-2.patchtext/x-diff; name=turn-elog-into-ereport-2.patchDownload
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 2a324c0b49..62e18961d3 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3179,7 +3179,7 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-INFO:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part_3_4" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3204,7 +3204,7 @@ CREATE TABLE part2 (
 	b int NOT NULL CHECK (b >= 10 AND b < 18)
 );
 ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
-INFO:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part2" is implied by existing constraints
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3219,7 +3219,7 @@ ERROR:  partition constraint is violated by some row
 DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-INFO:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part_5" is implied by existing constraints
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
#232Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#231)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/21 14:03, Amit Langote wrote:

OK, updated patch attached.

Oops, incomplete patch that was. Complete patch attached this time.

Thanks,
Amit

Attachments:

turn-elog-into-ereport-2.patchtext/x-diff; name=turn-elog-into-ereport-2.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..115b98313e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13297,8 +13297,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		}
 	}
 
+	/* It's safe to skip the validation scan after all */
 	if (skip_validate)
-		elog(NOTICE, "skipping scan to validate partition constraint");
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(attachRel))));
 
 	/*
 	 * Set up to have the table to be scanned to validate the partition
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 99e20eb922..62e18961d3 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3179,7 +3179,7 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part_3_4" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3204,7 +3204,7 @@ CREATE TABLE part2 (
 	b int NOT NULL CHECK (b >= 10 AND b < 18)
 );
 ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part2" is implied by existing constraints
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3219,7 +3219,7 @@ ERROR:  partition constraint is violated by some row
 DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-NOTICE:  skipping scan to validate partition constraint
+INFO:  partition constraint for table "part_5" is implied by existing constraints
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
#233Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#228)
3 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/21 1:53, Robert Haas wrote:

On Mon, Dec 19, 2016 at 10:59 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Dec 18, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Here are updated patches including the additional information.

Thanks. Committed 0001. Will have to review the others when I'm less tired.

0002. Can you add a test case for the bug fixed by this patch?

Done (also see below).

0003. Loses equalTupleDescs() check and various cases where
ExecOpenIndexes can be skipped. Isn't that bad?

I realized that as far as checking whether tuple conversion mapping is
required, the checks performed by convert_tuples_by_name() at the
beginning of the function following the comment /* Verify compatibility
and prepare attribute-number map */ are enough. If equalTupleDescs()
returned false (which it always does because tdtypeid are never the same
for passed in tuple descriptors), we would be repeating some of the tests
in convert_tuples_by_name() anyway.

As for the checks performed for ExecOpenIndices(), it seems would be
better to keep the following in place, so added back:

if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
leaf_part_rri->ri_IndexRelationDescs == NULL)
ExecOpenIndices(leaf_part_rri, false);

Also, "We locked all
the partitions above including the leaf partitions" should say "Caller
must have locked all the partitions including the leaf partitions".

No, we do the locking in RelationGetPartitionDispatchInfo(), which is
called by ExecSetupPartitionTupleRouting() itself.

In ExecSetupPartitionTupleRouting() is the first time we lock all the
partitions.

0004. Unnecessary whitespace change in executor.h. Still don't
understand why we need to hoist RelationGetPartitionQual() into the
caller.

We only need to check a result relation's (ri_RelationDesc's) partition
constraint if we are inserting into the result relation directly. In case
of tuple-routing, we do not want to check the leaf partitions' partition
constraint, but if the direct target in that case is an internal
partition, we must check its partition constraint, which is same for all
leaf partitions in that sub-tree. It wouldn't be wrong per se to check
each leaf partition's constraint in that case, which includes the target
partitioned table's constraint as well, but that would inefficient due to
both having to retrieve the constraints and having ExecConstraints()
*unnecessarily* check it for every row.

If we keep doing RelationGetPartitionQual() in InitResultRelInfo()
controlled by a bool argument (load_partition_check), we cannot avoid the
above mentioned inefficiency if we want to fix this bug.

0005. Can you add a test case for the bug fixed by this patch?

Done, but...

Breaking changes into multiple commits/patches does not seem to work for
adding regression tests. So, I've combined multiple patches into a single
patch which is now patch 0002 in the attached set of patches. Its commit
message is very long now. To show an example of bugs that 0002 is meant for:

create table p (a int, b int) partition by range (a, b);
create table p1 (b int, a int not null) partition by range (b);
create table p11 (like p1);
alter table p11 drop a;
alter table p11 add a int;
alter table p11 drop a;
alter table p11 add a int not null;

# select attrelid::regclass, attname, attnum
from pg_attribute
where attnum > 0
and (attrelid = 'p'::regclass
or attrelid = 'p1'::regclass
or attrelid = 'p11'::regclass) and attname = 'a';
attrelid | attname | attnum
----------+---------+--------
p | a | 1
p1 | a | 2
p11 | a | 4
(3 rows)

alter table p1 attach partition p11 for values from (1) to (5);
alter table p attach partition p1 for values from (1, 1) to (1, 10);

-- the following is wrong
# insert into p11 (a, b) values (10, 4);
INSERT 0 1

-- wrong too (using the wrong TupleDesc after tuple routing)
# insert into p1 (a, b) values (10, 4);
ERROR: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (4, null).

-- once we fix the wrong TupleDesc issue
# insert into p1 (a, b) values (10, 4);
INSERT 0 1

which is wrong because p1, as a partition of p, should not accept 10 for
a. But its partition constraint is not being applied to the leaf
partition p11 into which the tuple is routed (the bug).

Thanks,
Amit

Attachments:

0001-Refactor-tuple-routing-setup-code.patchtext/x-diff; name=0001-Refactor-tuple-routing-setup-code.patchDownload
From d21ae74ae01e93f21df5b84ed097ebbc85e529dd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 15:56:27 +0900
Subject: [PATCH 1/3] Refactor tuple-routing setup code

It's unnecessarily repeated in copy.c and nodeModifyTable.c, which makes
it a burden to maintain.  Should've been like this to begin with.

I moved the common code to ExecSetupPartitionTupleRouting() in execMain.c
that also houses ExecFindParttion() currently.  Hmm, should there be a
new src/backend/executor/execPartition.c?

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 72 ++++++--------------------
 src/backend/executor/execMain.c        | 92 ++++++++++++++++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c | 76 ++++++----------------------
 src/include/executor/executor.h        |  5 ++
 4 files changed, 127 insertions(+), 118 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a8da338f0..d5901651db 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1406,64 +1406,22 @@ BeginCopy(ParseState *pstate,
 		/* Initialize state for CopyFrom tuple routing. */
 		if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		{
-			List	   *leaf_parts;
-			ListCell   *cell;
-			int			i,
-						num_parted;
-			ResultRelInfo *leaf_part_rri;
-
-			/* Get the tuple-routing information and lock partitions */
-			cstate->partition_dispatch_info =
-				RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-												 &num_parted,
-												 &leaf_parts);
+			PartitionDispatch  *partition_dispatch_info;
+			ResultRelInfo	   *partitions;
+			TupleConversionMap **partition_tupconv_maps;
+			int					num_parted,
+								num_partitions;
+
+			ExecSetupPartitionTupleRouting(rel,
+										   &partition_dispatch_info,
+										   &partitions,
+										   &partition_tupconv_maps,
+										   &num_parted, &num_partitions);
+			cstate->partition_dispatch_info = partition_dispatch_info;
 			cstate->num_dispatch = num_parted;
-			cstate->num_partitions = list_length(leaf_parts);
-			cstate->partitions = (ResultRelInfo *)
-				palloc(cstate->num_partitions *
-					   sizeof(ResultRelInfo));
-			cstate->partition_tupconv_maps = (TupleConversionMap **)
-				palloc0(cstate->num_partitions *
-						sizeof(TupleConversionMap *));
-
-			leaf_part_rri = cstate->partitions;
-			i = 0;
-			foreach(cell, leaf_parts)
-			{
-				Relation	partrel;
-
-				/*
-				 * We locked all the partitions above including the leaf
-				 * partitions.  Note that each of the relations in
-				 * cstate->partitions will be closed by CopyFrom() after it's
-				 * finished with its processing.
-				 */
-				partrel = heap_open(lfirst_oid(cell), NoLock);
-
-				/*
-				 * Verify result relation is a valid target for the current
-				 * operation.
-				 */
-				CheckValidResultRel(partrel, CMD_INSERT);
-
-				InitResultRelInfo(leaf_part_rri,
-								  partrel,
-								  1,	/* dummy */
-								  false,		/* no partition constraint
-												 * check */
-								  0);
-
-				/* Open partition indices */
-				ExecOpenIndices(leaf_part_rri, false);
-
-				if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
-					cstate->partition_tupconv_maps[i] =
-						convert_tuples_by_name(tupDesc,
-											   RelationGetDescr(partrel),
-								 gettext_noop("could not convert row type"));
-				leaf_part_rri++;
-				i++;
-			}
+			cstate->partitions = partitions;
+			cstate->num_partitions = num_partitions;
+			cstate->partition_tupconv_maps = partition_tupconv_maps;
 		}
 	}
 	else
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d43a204808..bca34a509c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "tcop/utility.h"
@@ -2999,6 +3000,97 @@ EvalPlanQualEnd(EPQState *epqstate)
 }
 
 /*
+ * ExecSetupPartitionTupleRouting - set up information needed during
+ * tuple routing for partitioned tables
+ *
+ * Output arguments:
+ * 'pd' receives an array of PartitionDispatch objects with one entry for
+ *		every partitioned table in the partition tree
+ * 'partitions' receives an array of ResultRelInfo objects with one entry for
+ *		every leaf partition in the partition tree
+ * 'tup_conv_maps' receives an array of TupleConversionMap objects with one
+ *		entry for every leaf partition (required to convert input tuple based
+ *		on the root table's rowtype to a leaf partition's rowtype after tuple
+ *		routing is done
+ * 'num_parted' receives the number of partitioned tables in the partition
+ *		tree (= the number of entries in the 'pd' output array)
+ * 'num_partitions' receives the number of leaf partitions in the partition
+ *		tree (= the number of entries in the 'partitions' and 'tup_conv_maps'
+ *		output arrays
+ *
+ * Note that all the relations in the partition tree are locked using the
+ * RowExclusiveLock mode upon return from this function.
+ */
+void
+ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions)
+{
+	TupleDesc	tupDesc = RelationGetDescr(rel);
+	List	   *leaf_parts;
+	ListCell   *cell;
+	int			i;
+	ResultRelInfo *leaf_part_rri;
+
+	/* Get the tuple-routing information and lock partitions */
+	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
+										   &leaf_parts);
+	*num_partitions = list_length(leaf_parts);
+	*partitions = (ResultRelInfo *) palloc(*num_partitions *
+										   sizeof(ResultRelInfo));
+	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
+										   sizeof(TupleConversionMap *));
+
+	leaf_part_rri = *partitions;
+	i = 0;
+	foreach(cell, leaf_parts)
+	{
+		Relation	partrel;
+		TupleDesc	part_tupdesc;
+
+		/*
+		 * We locked all the partitions above including the leaf partitions.
+		 * Note that each of the relations in *partitions are eventually
+		 * closed by the caller.
+		 */
+		partrel = heap_open(lfirst_oid(cell), NoLock);
+		part_tupdesc = RelationGetDescr(partrel);
+
+		/*
+		 * Verify result relation is a valid target for the current operation.
+		 */
+		CheckValidResultRel(partrel, CMD_INSERT);
+
+		/*
+		 * Save a tuple conversion map to convert a tuple routed to this
+		 * partition from the parent's type to the partition's.
+		 */
+		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+								 gettext_noop("could not convert row type"));
+
+		InitResultRelInfo(leaf_part_rri,
+						  partrel,
+						  1,	 /* dummy */
+						  false,
+						  0);
+
+		/*
+		 * Open partition indices (remember we do not support ON CONFLICT in
+		 * case of partitioned tables, so we do not need support information
+		 * for speculative insertion)
+		 */
+		if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+			leaf_part_rri->ri_IndexRelationDescs == NULL)
+			ExecOpenIndices(leaf_part_rri, false);
+
+		leaf_part_rri++;
+		i++;
+	}
+}
+
+/*
  * ExecFindPartition -- Find a leaf partition in the partition tree rooted
  * at parent, for the heap tuple contained in *slot
  *
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ec440b353d..a9546106ce 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1718,68 +1718,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (operation == CMD_INSERT &&
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
-		int					i,
-							j,
-							num_parted;
-		List			   *leaf_parts;
-		ListCell		   *cell;
-		ResultRelInfo	   *leaf_part_rri;
-
-		/* Get the tuple-routing information and lock partitions */
-		mtstate->mt_partition_dispatch_info =
-					RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
-													 &num_parted,
-													 &leaf_parts);
+		PartitionDispatch  *partition_dispatch_info;
+		ResultRelInfo	   *partitions;
+		TupleConversionMap **partition_tupconv_maps;
+		int					num_parted,
+							num_partitions;
+
+		ExecSetupPartitionTupleRouting(rel,
+									   &partition_dispatch_info,
+									   &partitions,
+									   &partition_tupconv_maps,
+									   &num_parted, &num_partitions);
+		mtstate->mt_partition_dispatch_info = partition_dispatch_info;
 		mtstate->mt_num_dispatch = num_parted;
-		mtstate->mt_num_partitions = list_length(leaf_parts);
-		mtstate->mt_partitions = (ResultRelInfo *)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(ResultRelInfo));
-		mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
-						palloc0(mtstate->mt_num_partitions *
-												sizeof(TupleConversionMap *));
-
-		leaf_part_rri = mtstate->mt_partitions;
-		i = j = 0;
-		foreach(cell, leaf_parts)
-		{
-			Oid			partrelid = lfirst_oid(cell);
-			Relation	partrel;
-
-			/*
-			 * We locked all the partitions above including the leaf
-			 * partitions.  Note that each of the relations in
-			 * mtstate->mt_partitions will be closed by ExecEndModifyTable().
-			 */
-			partrel = heap_open(partrelid, NoLock);
-
-			/*
-			 * Verify result relation is a valid target for the current
-			 * operation
-			 */
-			CheckValidResultRel(partrel, CMD_INSERT);
-
-			InitResultRelInfo(leaf_part_rri,
-							  partrel,
-							  1,		/* dummy */
-							  false,	/* no partition constraint checks */
-							  eflags);
-
-			/* Open partition indices (note: ON CONFLICT unsupported)*/
-			if (partrel->rd_rel->relhasindex && operation != CMD_DELETE &&
-				leaf_part_rri->ri_IndexRelationDescs == NULL)
-				ExecOpenIndices(leaf_part_rri, false);
-
-			if (!equalTupleDescs(RelationGetDescr(rel),
-								 RelationGetDescr(partrel)))
-				mtstate->mt_partition_tupconv_maps[i] =
-							convert_tuples_by_name(RelationGetDescr(rel),
-												   RelationGetDescr(partrel),
-								  gettext_noop("could not convert row type"));
-
-			leaf_part_rri++;
-			i++;
-		}
+		mtstate->mt_partitions = partitions;
+		mtstate->mt_num_partitions = num_partitions;
+		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
 	}
 
 	/*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3f649faf2f..b74fa5eb5d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -213,6 +213,11 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
 extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern void ExecSetupPartitionTupleRouting(Relation rel,
+							   PartitionDispatch **pd,
+							   ResultRelInfo **partitions,
+							   TupleConversionMap ***tup_conv_maps,
+							   int *num_parted, int *num_partitions);
 extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
 				  TupleTableSlot *slot,
-- 
2.11.0

0002-Fix-multiple-issues-with-partition-constraints.patchtext/x-diff; name=0002-Fix-multiple-issues-with-partition-constraints.patchDownload
From 8c55a95e538e07d4b74d2985f01684749c791708 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 2/3] Fix multiple issues with partition constraints

1. A bug in how they are generated

Firstly, since we always want to recurse when calling
RelationGetPartitionQual(), ie, consider the parent's partition
constraint (if any), get rid of the argument recurse; also in
the module-local generate_partition_qual() that it calls.

Move the code for doing parent attnos to child attnos mapping for
Vars in partition constraint expressions to a separate function
map_partition_varattnos() and call it from appropriate places.  Doing
it in get_qual_from_partbound(), as is now, would produce wrong result
in certain multi-level partitioning cases, because it only considers
the current pair of parent-child relations.  In certain multi-level
partitioning cases, attnums for the same key attribute(s) might differ
between multiple pairs of consecutive levels causing the same attribute
to be numbered differently in different Vars of the same expression
tree.  Remember that we apply the whole partition constraint (list of
constraints of partitions at various levels) to a single (leaf
partition) relation.

With this commit, in generate_partition_qual(), we first generate the
the whole partition constraint (considering all levels of partitioning)
and then do the mapping from the root parent attnums to leaf partition
attnums.

2. A bug when attaching a partitioned table as partition

ATExecAttachPartition() failed to do the attnum mapping in the case where
attached partition is a partitioned table.  It is possible in such case
that the leaf partitions of such a table that will be scanned for partition
constraint validation might have different attnums than their parent.
That might lead to incorrect results.

3. A bug when inserting into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

InitResultRelInfo()'s API changes with this.  Instead of passing
a boolean telling whether or not to load the partition constraint,
callers now need to pass the exact constraint expression to use
as ri_PartitionCheck or NIL.

4. A bug due to using the wrong TupleTableSlot after tuple routing

We must use the partition's tuple descriptor *after* a tuple is routed,
not the root table's.  Partition's attributes, for example, may be
ordered diferently from the root table's, causing spurious not-null
violation errors due to asking for out-of-range attnum from a heap
tuple and getting null in return.

We must then switch back to the root table's for the next tuple, because
computing partition key of a tuple to be routed must be looking at the
root table's tuple descriptor.  A dedicated TupleTableSlot is allocated
within EState called es_partition_tuple_slot whose descriptor is set to
a given leaf partition for every input tuple after it's routed.

5. A bug whereby ExecConstraints() shows wrong input row in error msgs

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root
table's rowtype and the partition's.

To convert back to the correct row format, keep root table relation
descriptor and a reverse tuple conversion map in the ResultRelInfo's
of leaf partitions.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c           | 100 +++++++++++++------------
 src/backend/commands/copy.c               |  35 ++++++++-
 src/backend/commands/tablecmds.c          |  11 ++-
 src/backend/executor/execMain.c           | 120 ++++++++++++++++++++++++++----
 src/backend/executor/nodeModifyTable.c    |  25 +++++++
 src/backend/optimizer/util/plancat.c      |   2 +-
 src/include/catalog/partition.h           |   3 +-
 src/include/executor/executor.h           |   4 +-
 src/include/nodes/execnodes.h             |   5 ++
 src/test/regress/expected/alter_table.out |  26 +++++++
 src/test/regress/expected/insert.out      |  36 +++++++++
 src/test/regress/sql/alter_table.sql      |  20 +++++
 src/test/regress/sql/insert.sql           |  22 ++++++
 13 files changed, 340 insertions(+), 69 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582b77..b6d0841c9b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,48 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +920,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /* Turn an array of OIDs with N elements into a list */
@@ -1445,7 +1451,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1459,6 +1465,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1466,20 +1476,18 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
 	/* Get pg_class.relpartbound */
-	if (!rel->rd_rel->relispartition)	/* should not happen */
-		elog(ERROR, "relation \"%s\" has relispartition = false",
-			 RelationGetRelationName(rel));
 	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
 	boundDatum = SysCacheGetAttr(RELOID, tuple,
 								 Anum_pg_class_relpartbound,
@@ -1492,18 +1500,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* this rel's partition qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d5901651db..9cd84e80c7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2294,6 +2294,7 @@ CopyFrom(CopyState cstate)
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
+	List	   *partcheck = NIL;
 
 #define MAX_BUFFERED_TUPLES 1000
 	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
@@ -2410,6 +2411,10 @@ CopyFrom(CopyState cstate)
 		hi_options |= HEAP_INSERT_FROZEN;
 	}
 
+	/* Don't forget the partition constraints */
+	if (cstate->rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(cstate->rel);
+
 	/*
 	 * We need a ResultRelInfo so we can use the regular executor's
 	 * index-entry-making machinery.  (There used to be a huge amount of code
@@ -2419,7 +2424,7 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
+					  partcheck, NULL, NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2436,6 +2441,15 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
+	 * Initialize a dedicated slot to manipulate tuples of any given
+	 * partition's rowtype.
+	 */
+	if (cstate->partition_dispatch_info)
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+	else
+		estate->es_partition_tuple_slot = NULL;
+
+	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2484,7 +2498,8 @@ CopyFrom(CopyState cstate)
 
 	for (;;)
 	{
-		TupleTableSlot *slot;
+		TupleTableSlot *slot,
+					   *oldslot = NULL;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2571,7 +2586,19 @@ CopyFrom(CopyState cstate)
 			map = cstate->partition_tupconv_maps[leaf_part_index];
 			if (map)
 			{
+				Relation	partrel = resultRelInfo->ri_RelationDesc;
+
 				tuple = do_convert_tuple(tuple, map);
+
+				/*
+				 * We must use the partition's tuple descriptor from this
+				 * point on.  Use a dedicated slot from this point on until
+				 * we're finished dealing with the partition.
+				 */
+				oldslot = slot;
+				slot = estate->es_partition_tuple_slot;
+				Assert(slot != NULL);
+				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 			}
 
@@ -2667,6 +2694,10 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
+
+				/* Switch back to the slot corresponding to the root table */
+				Assert(oldslot != NULL);
+				slot = oldslot;
 			}
 		}
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c219b03dd..67ff1715ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
+						  NIL, NULL, NULL,
 						  0);
 		resultRelInfo++;
 	}
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13323,6 +13323,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13345,8 +13346,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..055efdcf9c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -821,13 +821,19 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			Index		resultRelationIndex = lfirst_int(l);
 			Oid			resultRelationOid;
 			Relation	resultRelation;
+			List	   *partcheck = NIL;
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
+			/* Don't forget the partition constraint */
+			if (resultRelation->rd_rel->relispartition)
+				partcheck = RelationGetPartitionQual(resultRelation);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
+							  partcheck, NULL, NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1217,7 +1223,9 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1255,10 +1263,13 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+	resultRelInfo->ri_PartitionCheck = partition_check;
+	/*
+	 * Following fields are only looked at in some tuple-routing cases.
+	 * In other case, they are set to NULL.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
+	resultRelInfo->ri_PartitionReverseMap = partition_reverse_map;
 }
 
 /*
@@ -1285,6 +1296,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	ListCell   *l;
 	Relation	rel;
 	MemoryContext oldcontext;
+	List	   *partcheck = NIL;
 
 	/* First, search through the query result relations */
 	rInfo = estate->es_result_relations;
@@ -1313,6 +1325,10 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	 */
 	rel = heap_open(relid, NoLock);
 
+	/* Don't forget the partition constraint */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	/*
 	 * Make the new entry in the right context.
 	 */
@@ -1321,7 +1337,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
+					  partcheck, NULL, NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1767,6 +1783,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * In case where the tuple is routed, it's been converted
+				 * to the partition's rowtype, which might differ from the
+				 * root table's.  We must convert it back to the root table's
+				 * type so that it's shown correctly in the error message.
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+					Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+					tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1780,9 +1816,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1794,6 +1830,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+				tuple = do_convert_tuple(tuple,
+									resultRelInfo->ri_PartitionReverseMap);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1806,9 +1856,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1816,6 +1866,20 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+			Assert(resultRelInfo->ri_PartitionReverseMap != NULL);
+			tuple = do_convert_tuple(tuple,
+									 resultRelInfo->ri_PartitionReverseMap);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1828,7 +1892,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3033,6 +3097,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	ListCell   *cell;
 	int			i;
 	ResultRelInfo *leaf_part_rri;
+	List		  *partcheck = NIL;
 
 	/* Get the tuple-routing information and lock partitions */
 	*pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock, num_parted,
@@ -3043,12 +3108,23 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
 										   sizeof(TupleConversionMap *));
 
+	/*
+	 * If the main target rel is a partition, ExecConstraints() as applied to
+	 * each leaf partition must consider its partition constraint, because
+	 * unlike explicit constraints, an implicit partition constraint is not
+	 * inherited.
+	 */
+	if (rel->rd_rel->relispartition)
+		partcheck = RelationGetPartitionQual(rel);
+
 	leaf_part_rri = *partitions;
 	i = 0;
 	foreach(cell, leaf_parts)
 	{
 		Relation	partrel;
 		TupleDesc	part_tupdesc;
+		List	   *my_check = NIL;
+		TupleConversionMap	*reverse_map;
 
 		/*
 		 * We locked all the partitions above including the leaf partitions.
@@ -3070,10 +3146,28 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
 								 gettext_noop("could not convert row type"));
 
+		/*
+		 * This is the parent's partition constraint, so any Vars in
+		 * it bear the its attribute numbers.  We must switch them to
+		 * the leaf partition's.
+		 */
+		if (partcheck)
+			my_check = map_partition_varattnos(partcheck, partrel, rel);
+
+		/*
+		 * We must save a reverse tuple conversion map as well, to show the
+		 * correct input tuple in the error message shown by ExecConstraints()
+		 * in case of routed tuples.  Remember that at the point of failure,
+		 * the tuple has been converted to the partition's type which might
+		 * not match the input tuple.
+		 */
+		reverse_map = convert_tuples_by_name(part_tupdesc, tupDesc,
+								 gettext_noop("could not convert row type"));
+
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
+						  my_check, rel, reverse_map,
 						  0);
 
 		/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a9546106ce..da4c96a863 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,6 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	TupleTableSlot *oldslot = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -318,7 +319,19 @@ ExecInsert(ModifyTableState *mtstate,
 		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
 		if (map)
 		{
+			Relation partrel = resultRelInfo->ri_RelationDesc;
+
 			tuple = do_convert_tuple(tuple, map);
+
+			/*
+			 * We must use the partition's tuple descriptor from this
+			 * point on, until we're finished dealing with the partition.
+			 * Use the dedicated slot for that.
+			 */
+			oldslot = slot;
+			slot = estate->es_partition_tuple_slot;
+			Assert(slot != NULL);
+			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 		}
 	}
@@ -566,6 +579,10 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
+
+		/* Switch back to the slot corresponding to the root table */
+		Assert(oldslot != NULL);
+		slot = oldslot;
 	}
 
 	/*
@@ -1734,7 +1751,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
+
+		/*
+		 * Initialize a dedicated slot to manipulate tuples of any given
+		 * partition's rowtype.
+		 */
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
 	}
+	else
+		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf87b..6ff821e6cf 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -70,7 +70,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5eb5d..1cc5f2eb94 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,9 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
+				  List *partition_check,
+				  Relation partition_root,
+				  TupleConversionMap *partition_reverse_map,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5c3b8683f5..49db19cc75 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,8 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
+	TupleConversionMap *ri_PartitionReverseMap;
 } ResultRelInfo;
 
 /* ----------------
@@ -384,6 +386,9 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
+	/* Slot used to manipulate a tuple after it is routed to a partition */
+	TupleTableSlot *es_partition_tuple_slot;
+
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 99e20eb922..0bf488884a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3317,3 +3317,29 @@ drop cascades to table part_2
 drop cascades to table part_5
 drop cascades to table part_5_a
 drop cascades to table part_1
+-- multi-level partitions with varying attnums for the same key attribute
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (1) to (5);
+-- check attnums for key attribute 'a'
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a' and (attrelid = 'p'::regclass or attrelid = 'p1'::regclass or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+-- check that attaching partition fails with violating rows in a leaf partition
+insert into p11 (a, b) values (10, 4);
+alter table p attach partition p1 for values from (1, 1) to (1, 10);
+ERROR:  partition constraint is violated by some row
+-- cleanup
+drop table p cascade;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 561cefa3c4..e785885723 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -300,3 +300,39 @@ drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+-- multi-level partitions with varying attnums for the same key attribute
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (1) to (5);
+alter table p attach partition p1 for values from (1, 1) to (1, 10);
+-- check attnums for key attribute 'a'
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a' and (attrelid = 'p'::regclass or attrelid = 'p1'::regclass or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+-- check that following inserts fail due to either partition constraint or tuple-routing failure
+insert into p11 (a, b) values (10, 4);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (4, 10).
+insert into p1 (a, b) values (10, 4);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (4, 10).
+insert into p (a, b) values (10, 4);
+ERROR:  no partition of relation "p" found for row
+DETAIL:  Failing row contains (10, 4).
+-- cleanup
+drop table p cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table p1
+drop cascades to table p11
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b285a406d9..2cd9d16648 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,3 +2169,23 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+
+-- multi-level partitions with varying attnums for the same key attribute
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (1) to (5);
+-- check attnums for key attribute 'a'
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a' and (attrelid = 'p'::regclass or attrelid = 'p1'::regclass or attrelid = 'p11'::regclass);
+-- check that attaching partition fails with violating rows in a leaf partition
+insert into p11 (a, b) values (10, 4);
+alter table p attach partition p1 for values from (1, 1) to (1, 10);
+
+-- cleanup
+drop table p cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 846bb5897a..656bae2d57 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -170,3 +170,25 @@ select tableoid::regclass, * from list_parted;
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
+
+-- multi-level partitions with varying attnums for the same key attribute
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (1) to (5);
+alter table p attach partition p1 for values from (1, 1) to (1, 10);
+-- check attnums for key attribute 'a'
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a' and (attrelid = 'p'::regclass or attrelid = 'p1'::regclass or attrelid = 'p11'::regclass);
+-- check that following inserts fail due to either partition constraint or tuple-routing failure
+insert into p11 (a, b) values (10, 4);
+insert into p1 (a, b) values (10, 4);
+insert into p (a, b) values (10, 4);
+
+-- cleanup
+drop table p cascade;
-- 
2.11.0

0003-Add-some-more-tests-for-tuple-routing.patchtext/x-diff; name=0003-Add-some-more-tests-for-tuple-routing.patchDownload
From 33c6af64f9eb3a36c920ff220215dc9efb1f174c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 21 Dec 2016 10:51:32 +0900
Subject: [PATCH 3/3] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 38 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 18 +++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index e785885723..b5307fd79a 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,34 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3_1 | Ff |    20 |    24
+ part_ee_ff3_2 | Ff |    25 |    29
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(9 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -293,13 +321,21 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff3_1
+drop cascades to table part_ee_ff3_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
 -- multi-level partitions with varying attnums for the same key attribute
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 656bae2d57..b3a8bda84c 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,24 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

#234Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#233)
Re: Declarative partitioning - another take

On Wed, Dec 21, 2016 at 5:33 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Breaking changes into multiple commits/patches does not seem to work for
adding regression tests. So, I've combined multiple patches into a single
patch which is now patch 0002 in the attached set of patches.

Ugh, seriously? It's fine to combine closely related bug fixes but
not all of these are. I don't see why you can't add some regression
tests in one patch and then add some more in the next patch.

Meanwhile, committed the latest 0001 and the elog() patch.

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

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

#235Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#234)
5 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/22 1:50, Robert Haas wrote:

On Wed, Dec 21, 2016 at 5:33 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Breaking changes into multiple commits/patches does not seem to work for
adding regression tests. So, I've combined multiple patches into a single
patch which is now patch 0002 in the attached set of patches.

Ugh, seriously? It's fine to combine closely related bug fixes but
not all of these are. I don't see why you can't add some regression
tests in one patch and then add some more in the next patch.

I managed to do that this time around with the attached set of patches.
Guess I gave up too easily in the previous attempt.

While working on that, I discovered yet-another-bug having to do with the
tuple descriptor that's used as we route a tuple down a partition tree. If
attnums of given key attribute(s) are different on different levels, it
would be incorrect to use the original slot's (one passed by ExecInsert())
tuple descriptor to inspect the original slot's heap tuple, as we go down
the tree. It might cause spurious "partition not found" at some level due
to looking at incorrect field in the input tuple because of using the
wrong tuple descriptor (root table's attnums not always same as other
partitioned tables in the tree). Patch 0001 fixes that including a test.
It also addresses the problem I mentioned previously that once
tuple-routing is done, we failed to switch to a slot with the leaf
partition's tupdesc (IOW, continued to use the original slot with root
table's tupdesc causing spurious failures due to differences in attums
between the leaf partition and the root table).

Further patches 0002, 0003 and 0004 fix bugs that I sent one-big-patch for
in my previous message. Each patch has a test for the bug it's meant to fix.

Patch 0005 is the same old "Add some more tests for tuple-routing" per [1]/messages/by-id/CA+TgmoZ86v1G+zx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA@mail.gmail.com:

Meanwhile, committed the latest 0001 and the elog() patch.

Thanks!

Regards,
Amit

[1]: /messages/by-id/CA+TgmoZ86v1G+zx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA@mail.gmail.com
/messages/by-id/CA+TgmoZ86v1G+zx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA@mail.gmail.com

Attachments:

0001-Use-correct-tuple-descriptor-before-and-after-routin.patchtext/x-diff; name=0001-Use-correct-tuple-descriptor-before-and-after-routin.patchDownload
From cac625b22990c12a537eaa4c77434017a2303b92 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 22 Dec 2016 15:33:41 +0900
Subject: [PATCH 1/5] Use correct tuple descriptor before and after routing a
 tuple

When we have a multi-level partitioned hierarchy where tables at
successive levels have different attnums for a partition key attribute(s),
we must use the tuple descriptor of a partitioned table of the given level
to inspect the appropriately converted representation of the input tuple
before computing the partition key to perform tuple-routing.

So, store in each PartitionDispatch object, a standalone TupleTableSlot
initialized with the TupleDesc of the corresponding partitioned table,
along with a TupleConversionMap to map tuples from the its parent's
rowtype to own rowtype.

After the routing is done and a leaf partition returned, we must use the
leaf partition's tuple descriptor, not the root table's.  For roughly
same reasons as above.  For the ext row and so on, we must then switch
back to the root table's tuple descriptor.  To that end, a dedicated
TupleTableSlot is allocated in EState called es_partition_tuple_slot,
whose descriptor is set to a given leaf partition for every input tuple
after it's routed.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c        | 74 ++++++++++++++++++++++++++++------
 src/backend/commands/copy.c            | 31 +++++++++++++-
 src/backend/executor/nodeModifyTable.c | 27 +++++++++++++
 src/include/catalog/partition.h        |  7 ++++
 src/include/nodes/execnodes.h          |  3 ++
 src/test/regress/expected/insert.out   | 37 +++++++++++++++++
 src/test/regress/sql/insert.sql        | 26 ++++++++++++
 7 files changed, 190 insertions(+), 15 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9980582b77..fca874752f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -923,13 +923,19 @@ RelationGetPartitionQual(Relation rel, bool recurse)
 	return generate_partition_qual(rel, recurse);
 }
 
-/* Turn an array of OIDs with N elements into a list */
-#define OID_ARRAY_TO_LIST(arr, N, list) \
+/*
+ * Append OIDs of rel's partitions to the list 'partoids' and for each OID,
+ * append pointer rel to the list 'parents'.
+ */
+#define APPEND_REL_PARTITION_OIDS(rel, partoids, parents) \
 	do\
 	{\
 		int		i;\
-		for (i = 0; i < (N); i++)\
-			(list) = lappend_oid((list), (arr)[i]);\
+		for (i = 0; i < (rel)->rd_partdesc->nparts; i++)\
+		{\
+			(partoids) = lappend_oid((partoids), (rel)->rd_partdesc->oids[i]);\
+			(parents) = lappend((parents), (rel));\
+		}\
 	} while(0)
 
 /*
@@ -944,11 +950,13 @@ PartitionDispatch *
 RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 								 int *num_parted, List **leaf_part_oids)
 {
-	PartitionDesc rootpartdesc = RelationGetPartitionDesc(rel);
 	PartitionDispatchData **pd;
 	List	   *all_parts = NIL,
-			   *parted_rels;
-	ListCell   *lc;
+			   *all_parents = NIL,
+			   *parted_rels,
+			   *parted_rel_parents;
+	ListCell   *lc1,
+			   *lc2;
 	int			i,
 				k,
 				offset;
@@ -965,10 +973,13 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 	 */
 	*num_parted = 1;
 	parted_rels = list_make1(rel);
-	OID_ARRAY_TO_LIST(rootpartdesc->oids, rootpartdesc->nparts, all_parts);
-	foreach(lc, all_parts)
+	/* Root partitioned table has no parent, so NULL for parent */
+	parted_rel_parents = list_make1(NULL);
+	APPEND_REL_PARTITION_OIDS(rel, all_parts, all_parents);
+	forboth(lc1, all_parts, lc2, all_parents)
 	{
-		Relation	partrel = heap_open(lfirst_oid(lc), lockmode);
+		Relation	partrel = heap_open(lfirst_oid(lc1), lockmode);
+		Relation	parent = lfirst(lc2);
 		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
 
 		/*
@@ -979,7 +990,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		{
 			(*num_parted)++;
 			parted_rels = lappend(parted_rels, partrel);
-			OID_ARRAY_TO_LIST(partdesc->oids, partdesc->nparts, all_parts);
+			parted_rel_parents = lappend(parted_rel_parents, parent);
+			APPEND_REL_PARTITION_OIDS(partrel, all_parts, all_parents);
 		}
 		else
 			heap_close(partrel, NoLock);
@@ -1004,10 +1016,12 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 										   sizeof(PartitionDispatchData *));
 	*leaf_part_oids = NIL;
 	i = k = offset = 0;
-	foreach(lc, parted_rels)
+	forboth(lc1, parted_rels, lc2, parted_rel_parents)
 	{
-		Relation	partrel = lfirst(lc);
+		Relation	partrel = lfirst(lc1);
+		Relation	parent = lfirst(lc2);
 		PartitionKey partkey = RelationGetPartitionKey(partrel);
+		TupleDesc	 tupdesc = RelationGetDescr(partrel);
 		PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
 		int			j,
 					m;
@@ -1017,6 +1031,27 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 		pd[i]->key = partkey;
 		pd[i]->keystate = NIL;
 		pd[i]->partdesc = partdesc;
+		if (parent != NULL)
+		{
+			/*
+			 * For every partitioned table other than root, we must store
+			 * a tuple table slot initialized with its tuple descriptor and
+			 * a tuple conversion map to convert a tuple from its parent's
+			 * rowtype to its own. That is to make sure that we are looking
+			 * at the correct row using the correct tuple descriptor when
+			 * computing its partition key for tuple routing.
+			 */
+			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
+			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
+												   tupdesc,
+								gettext_noop("could not convert row type"));
+		}
+		else
+		{
+			/* Not required for the root partitioned table */
+			pd[i]->tupslot = NULL;
+			pd[i]->tupmap = NULL;
+		}
 		pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
 
 		/*
@@ -1610,6 +1645,8 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	{
 		PartitionKey key = parent->key;
 		PartitionDesc partdesc = parent->partdesc;
+		TupleTableSlot *myslot = parent->tupslot;
+		TupleConversionMap *map = parent->tupmap;
 
 		/* Quick exit */
 		if (partdesc->nparts == 0)
@@ -1618,6 +1655,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
+		if (myslot != NULL)
+		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+
+			ExecClearTuple(myslot);
+			Assert(map != NULL);
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
+			slot = myslot;
+		}
+
 		/* Extract partition key from tuple */
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d5901651db..aa25a23336 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2436,6 +2436,15 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
+	 * Initialize a dedicated slot to manipulate tuples of any given
+	 * partition's rowtype.
+	 */
+	if (cstate->partition_dispatch_info)
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+	else
+		estate->es_partition_tuple_slot = NULL;
+
+	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2484,7 +2493,8 @@ CopyFrom(CopyState cstate)
 
 	for (;;)
 	{
-		TupleTableSlot *slot;
+		TupleTableSlot *slot,
+					   *oldslot = NULL;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2571,7 +2581,19 @@ CopyFrom(CopyState cstate)
 			map = cstate->partition_tupconv_maps[leaf_part_index];
 			if (map)
 			{
+				Relation	partrel = resultRelInfo->ri_RelationDesc;
+
 				tuple = do_convert_tuple(tuple, map);
+
+				/*
+				 * We must use the partition's tuple descriptor from this
+				 * point on.  Use a dedicated slot from this point on until
+				 * we're finished dealing with the partition.
+				 */
+				oldslot = slot;
+				slot = estate->es_partition_tuple_slot;
+				Assert(slot != NULL);
+				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 			}
 
@@ -2667,6 +2689,10 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
+
+				/* Switch back to the slot corresponding to the root table */
+				Assert(oldslot != NULL);
+				slot = oldslot;
 			}
 		}
 	}
@@ -2714,13 +2740,14 @@ CopyFrom(CopyState cstate)
 		 * Remember cstate->partition_dispatch_info[0] corresponds to the root
 		 * partitioned table, which we must not try to close, because it is
 		 * the main target table of COPY that will be closed eventually by
-		 * DoCopy().
+		 * DoCopy().  Also, tupslot is NULL for the root partitioned table.
 		 */
 		for (i = 1; i < cstate->num_dispatch; i++)
 		{
 			PartitionDispatch pd = cstate->partition_dispatch_info[i];
 
 			heap_close(pd->reldesc, NoLock);
+			ExecDropSingleTupleTableSlot(pd->tupslot);
 		}
 		for (i = 0; i < cstate->num_partitions; i++)
 		{
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a9546106ce..0d85b151c2 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,6 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	TupleTableSlot *oldslot = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -318,7 +319,19 @@ ExecInsert(ModifyTableState *mtstate,
 		map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
 		if (map)
 		{
+			Relation partrel = resultRelInfo->ri_RelationDesc;
+
 			tuple = do_convert_tuple(tuple, map);
+
+			/*
+			 * We must use the partition's tuple descriptor from this
+			 * point on, until we're finished dealing with the partition.
+			 * Use the dedicated slot for that.
+			 */
+			oldslot = slot;
+			slot = estate->es_partition_tuple_slot;
+			Assert(slot != NULL);
+			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
 		}
 	}
@@ -566,6 +579,10 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
+
+		/* Switch back to the slot corresponding to the root table */
+		Assert(oldslot != NULL);
+		slot = oldslot;
 	}
 
 	/*
@@ -1734,7 +1751,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
+
+		/*
+		 * Initialize a dedicated slot to manipulate tuples of any given
+		 * partition's rowtype.
+		 */
+		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
 	}
+	else
+		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
@@ -2058,12 +2083,14 @@ ExecEndModifyTable(ModifyTableState *node)
 	 * Remember node->mt_partition_dispatch_info[0] corresponds to the root
 	 * partitioned table, which we must not try to close, because it is the
 	 * main target table of the query that will be closed by ExecEndPlan().
+	 * Also, tupslot is NULL for the root partitioned table.
 	 */
 	for (i = 1; i < node->mt_num_dispatch; i++)
 	{
 		PartitionDispatch pd = node->mt_partition_dispatch_info[i];
 
 		heap_close(pd->reldesc, NoLock);
+		ExecDropSingleTupleTableSlot(pd->tupslot);
 	}
 	for (i = 0; i < node->mt_num_partitions; i++)
 	{
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 21effbf87b..bf38df5d29 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -47,6 +47,11 @@ typedef struct PartitionDescData *PartitionDesc;
  *	key			Partition key information of the table
  *	keystate	Execution state required for expressions in the partition key
  *	partdesc	Partition descriptor of the table
+ *	tupslot		A standalone TupleTableSlot initialized with this table's tuple
+ *				descriptor
+ *	tupmap		TupleConversionMap to convert from the parent's rowtype to
+ *				this table's rowtype (when extracting the partition key of a
+ *				tuple just before routing it through this table)
  *	indexes		Array with partdesc->nparts members (for details on what
  *				individual members represent, see how they are set in
  *				RelationGetPartitionDispatchInfo())
@@ -58,6 +63,8 @@ typedef struct PartitionDispatchData
 	PartitionKey			key;
 	List				   *keystate;	/* list of ExprState */
 	PartitionDesc			partdesc;
+	TupleTableSlot		   *tupslot;
+	TupleConversionMap	   *tupmap;
 	int					   *indexes;
 } PartitionDispatchData;
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5c3b8683f5..9073a9a1bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -384,6 +384,9 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
+	/* Slot used to manipulate a tuple after it is routed to a partition */
+	TupleTableSlot *es_partition_tuple_slot;
+
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 561cefa3c4..49f667b119 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -300,3 +300,40 @@ drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+-- check that "(1, 2)" is correctly routed to p11.
+insert into p values (1, 2);
+select tableoid::regclass, * from p;
+ tableoid | a | b 
+----------+---+---
+ p11      | 1 | 2
+(1 row)
+
+-- cleanup
+drop table p cascade;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table p1
+drop cascades to table p11
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 846bb5897a..08dc068de8 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -170,3 +170,29 @@ select tableoid::regclass, * from list_parted;
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
+
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+-- check that "(1, 2)" is correctly routed to p11.
+insert into p values (1, 2);
+select tableoid::regclass, * from p;
+
+-- cleanup
+drop table p cascade;
-- 
2.11.0

0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchtext/x-diff; name=0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchDownload
From c6df09f19533d2ee22882138eafa26c2fc82e261 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 18:00:47 +0900
Subject: [PATCH 2/5] Make ExecConstraints() show the correct row in error msgs

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root table's
(ie, the table into which the row is inserted in a given query) rowtype
and the partition's.

To fix, convert the erring row back to root table's format before
printing the error message showing the row.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 +
 src/backend/commands/tablecmds.c     |  1 +
 src/backend/executor/execMain.c      | 75 +++++++++++++++++++++++++++++++++---
 src/include/executor/executor.h      |  1 +
 src/include/nodes/execnodes.h        |  1 +
 src/test/regress/expected/insert.out |  7 ++++
 src/test/regress/sql/insert.sql      |  6 +++
 7 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index aa25a23336..a20e22daaf 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2420,6 +2420,7 @@ CopyFrom(CopyState cstate)
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
 					  true,		/* do load partition check expression */
+					  NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 115b98313e..338ff08ed4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,6 +1324,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 						  rel,
 						  0,	/* dummy rangetable index */
 						  false,
+						  NULL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..55febd7bc1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,6 +828,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 							  resultRelation,
 							  resultRelationIndex,
 							  true,
+							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1218,6 +1219,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1259,6 +1261,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		resultRelInfo->ri_PartitionCheck =
 							RelationGetPartitionQual(resultRelationDesc,
 													 true);
+	/*
+	 * The following gets set to NULL unless we are initializing leaf
+	 * partitions for tuple-routing.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
 /*
@@ -1322,6 +1329,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 					  rel,
 					  0,		/* dummy rangetable index */
 					  true,
+					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1767,6 +1775,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * In case where the tuple is routed, it's been converted
+				 * to the partition's rowtype, which might differ from the
+				 * root table's.  We must convert it back to the root table's
+				 * type so that it's shown correctly in the error message.
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TupleConversionMap	*map;
+
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+					/* a reverse map */
+					map = convert_tuples_by_name(orig_tupdesc, tupdesc,
+								gettext_noop("could not convert row type"));
+					tuple = do_convert_tuple(tuple, map);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1780,9 +1810,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1794,6 +1824,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+			TupleDesc	orig_tupdesc = tupdesc;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TupleConversionMap	*map;
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				/* a reverse map */
+				map = convert_tuples_by_name(orig_tupdesc, tupdesc,
+							gettext_noop("could not convert row type"));
+				tuple = do_convert_tuple(tuple, map);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1806,9 +1853,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1816,6 +1863,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+		TupleDesc	orig_tupdesc = tupdesc;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+			TupleConversionMap	*map;
+
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+			/* a reverse map */
+			map = convert_tuples_by_name(orig_tupdesc, tupdesc,
+						gettext_noop("could not convert row type"));
+			tuple = do_convert_tuple(tuple, map);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
@@ -1828,7 +1892,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3074,6 +3138,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 						  partrel,
 						  1,	 /* dummy */
 						  false,
+						  rel,
 						  0);
 
 		/*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5eb5d..f85b26b00a 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -190,6 +190,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9073a9a1bc..97b4083f14 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,7 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 49f667b119..b120954997 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -332,6 +332,13 @@ select tableoid::regclass, * from p;
  p11      | 1 | 2
 (1 row)
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+ERROR:  new row for relation "p11" violates check constraint "check_b"
+DETAIL:  Failing row contains (1, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 08dc068de8..3d2fdb92c5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -194,5 +194,11 @@ alter table p attach partition p1 for values from (1, 2) to (1, 10);
 insert into p values (1, 2);
 select tableoid::regclass, * from p;
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchtext/x-diff; name=0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchDownload
From 1df7ca48a471b19aa61f11cb9c2390d6bbbce980 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 3/5] Fix a bug in how we generate partition constraints

Firstly, since we always want to recurse when calling
RelationGetPartitionQual(), ie, consider the parent's partition
constraint (if any), get rid of the argument recurse; also in the
module-local generate_partition_qual() that it calls.

Move the code for doing parent attnos to child attnos mapping for Vars
in partition constraint expressions to a separate function
map_partition_varattnos() and call it from the appropriate places.
Doing it in get_qual_from_partbound(), as is now, would produce wrong
result in certain multi-level partitioning cases, because it only
considers the current pair of parent-child relations.  In certain
multi-level partitioning cases, attnums for the same key attribute(s)
might differ between different pairs of consecutive levels causing the
same attribute to be numbered differently in different Vars of the same
expression tree.  Remember that we apply the whole partition constraint
(list of constraints of partitions at various levels) to a single (leaf
partition) relation.

With this commit, in generate_partition_qual(), we first generate the
the whole partition constraint (considering all levels of partitioning)
and then do the mapping from the root parent attnums to leaf partition
attnums.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c           | 103 ++++++++++++++++--------------
 src/backend/commands/tablecmds.c          |   9 ++-
 src/backend/executor/execMain.c           |   4 +-
 src/backend/optimizer/util/plancat.c      |   2 +-
 src/include/catalog/partition.h           |   3 +-
 src/test/regress/expected/alter_table.out |  30 +++++++++
 src/test/regress/sql/alter_table.sql      |  25 ++++++++
 7 files changed, 122 insertions(+), 54 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fca874752f..34ab812b44 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,51 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	if (expr == NIL)
+		return NIL;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +923,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /*
@@ -1480,7 +1489,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1494,6 +1503,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1501,20 +1514,18 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
 	/* Get pg_class.relpartbound */
-	if (!rel->rd_rel->relispartition)	/* should not happen */
-		elog(ERROR, "relation \"%s\" has relispartition = false",
-			 RelationGetRelationName(rel));
 	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
 	boundDatum = SysCacheGetAttr(RELOID, tuple,
 								 Anum_pg_class_relpartbound,
@@ -1527,18 +1538,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* this rel's partition qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 338ff08ed4..3db830f0c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13152,7 +13152,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13327,6 +13327,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13349,8 +13350,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 55febd7bc1..6954ba8d32 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1259,8 +1259,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
 	 * The following gets set to NULL unless we are initializing leaf
 	 * partitions for tuple-routing.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index bf38df5d29..78220d6ac6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 62e18961d3..0a1d1db54e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3317,3 +3317,33 @@ drop cascades to table part_2
 drop cascades to table part_5
 drop cascades to table part_5_a
 drop cascades to table part_1
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+alter table p1 attach partition p11 for values from (2) to (5);
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+ERROR:  partition constraint is violated by some row
+-- cleanup
+drop table p, p1 cascade;
+NOTICE:  drop cascades to table p11
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b285a406d9..ce7e85b6ad 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,3 +2169,28 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+
+alter table p1 attach partition p11 for values from (2) to (5);
+
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+-- cleanup
+drop table p, p1 cascade;
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 7bcec1a4ddb0b397232033e8faa0533e9bdf26a1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/5] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 41 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a20e22daaf..2cb89190e7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2419,7 +2419,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3db830f0c7..096ca181be 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 6954ba8d32..3e2c1cb790 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,14 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
 
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1328,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3137,7 +3161,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index f85b26b00a..9c7878f847 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b120954997..6a6aac5a88 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -339,6 +339,12 @@ alter table p add constraint check_b check (b = 3);
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 2).
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3d2fdb92c5..171196df6d 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -200,5 +200,10 @@ alter table p add constraint check_b check (b = 3);
 -- after "(1, 2)" is routed to it
 insert into p values (1, 2);
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0005-Add-some-more-tests-for-tuple-routing.patchtext/x-diff; name=0005-Add-some-more-tests-for-tuple-routing.patchDownload
From 19a99fb5b550f032bc8586932f67bba024858c5e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 21 Dec 2016 10:51:32 +0900
Subject: [PATCH 5/5] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 38 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 18 +++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6a6aac5a88..ed0513d2ff 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,34 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3_1 | Ff |    20 |    24
+ part_ee_ff3_2 | Ff |    25 |    29
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(9 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -293,13 +321,21 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff3_1
+drop cascades to table part_ee_ff3_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 171196df6d..dca89b9286 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,24 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

#236Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#235)
Re: Declarative partitioning - another take

On Thu, Dec 22, 2016 at 3:35 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

While working on that, I discovered yet-another-bug having to do with the
tuple descriptor that's used as we route a tuple down a partition tree. If
attnums of given key attribute(s) are different on different levels, it
would be incorrect to use the original slot's (one passed by ExecInsert())
tuple descriptor to inspect the original slot's heap tuple, as we go down
the tree. It might cause spurious "partition not found" at some level due
to looking at incorrect field in the input tuple because of using the
wrong tuple descriptor (root table's attnums not always same as other
partitioned tables in the tree). Patch 0001 fixes that including a test.

I committed this, but I'm a bit uncomfortable with it: should the
TupleTableSlot be part of the ModifyTableState rather than the EState?

It also addresses the problem I mentioned previously that once
tuple-routing is done, we failed to switch to a slot with the leaf
partition's tupdesc (IOW, continued to use the original slot with root
table's tupdesc causing spurious failures due to differences in attums
between the leaf partition and the root table).

Further patches 0002, 0003 and 0004 fix bugs that I sent one-big-patch for
in my previous message. Each patch has a test for the bug it's meant to fix.

Regarding 0002, I think that this is kind of a strange fix. Wouldn't
it be better to get hold of the original tuple instead of reversing
the conversion? And what of the idea of avoiding the conversion in
the (probably very common) case where we can?

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

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

#237Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#236)
7 attachment(s)
Re: Declarative partitioning - another take

Sorry about the delay in replying.

On 2016/12/23 8:08, Robert Haas wrote:

On Thu, Dec 22, 2016 at 3:35 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

While working on that, I discovered yet-another-bug having to do with the
tuple descriptor that's used as we route a tuple down a partition tree. If
attnums of given key attribute(s) are different on different levels, it
would be incorrect to use the original slot's (one passed by ExecInsert())
tuple descriptor to inspect the original slot's heap tuple, as we go down
the tree. It might cause spurious "partition not found" at some level due
to looking at incorrect field in the input tuple because of using the
wrong tuple descriptor (root table's attnums not always same as other
partitioned tables in the tree). Patch 0001 fixes that including a test.

I committed this, but I'm a bit uncomfortable with it: should the
TupleTableSlot be part of the ModifyTableState rather than the EState?

Done that way in 0001 of the attached patches. So, instead of making the
standalone partition_tuple_slot a field of EState (with the actual
TupleTableSlot in its tupleTable), it is now allocated within
ModifyTableState and CopyState, and released when ModifyTable node or
CopyFrom finishes, respectively.

It also addresses the problem I mentioned previously that once
tuple-routing is done, we failed to switch to a slot with the leaf
partition's tupdesc (IOW, continued to use the original slot with root
table's tupdesc causing spurious failures due to differences in attums
between the leaf partition and the root table).

Further patches 0002, 0003 and 0004 fix bugs that I sent one-big-patch for
in my previous message. Each patch has a test for the bug it's meant to fix.

Regarding 0002, I think that this is kind of a strange fix. Wouldn't
it be better to get hold of the original tuple instead of reversing
the conversion? And what of the idea of avoiding the conversion in
the (probably very common) case where we can?

To get hold of the original tuple, how about adding an argument orig_slot
to ExecConstraints()? I've implemented that approach in the new 0002.

Regarding the possibility of avoiding the conversion in very common cases,
I think that could be done considering the following: If the mapping from
the attribute numbers of the parent table to that of a child table is an
identity map, we don't need to convert tuples. Currently however,
convert_tuples_by_name() also requires tdtypeid of the input and output
TupleDescs to be equal. The reason cited for that is that we may fail to
"inject the right OID into the tuple datum" if the types don't match. In
case of partitioning, hasoid status must match between the parent and its
partitions at all times, so the aforementioned condition is satisfied
without requiring that tdtypeid are same. And oid column (if present) is
always located at a given position in HeapTuple, so need not map that.

Based on the above argument, patch 0006 teaches convert_tuples_by_name()
to *optionally* not require tdtypeid of input and output tuple descriptors
to be equal. It's implemented by introducing a new argument to
convert_tuples_by_name() named 'consider_typeid'. We pass 'false' only
for the partitioning cases.

(Perhaps, the following should be its own new thread)

I noticed that ExecProcessReturning() doesn't work properly after tuple
routing (example shows how returning tableoid currently fails but I
mention some other issues below):

create table p (a int, b int) partition by range (a);
create table p1 partition of p for values from (1) to (10);
insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
- | 1 |
(1 row)

INSERT 0 1

I tried to fix that in 0007 to get:

insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
p | 1 |
(1 row)

INSERT 0 1

But I think it *may* be wrong to return the root table OID for tuples
inserted into leaf partitions, because with select we get partition OIDs:

select tableoid::regclass, * from p;
tableoid | a | b
----------+---+---
p1 | 1 |
(1 row)

If so, that means we should build the projection info (corresponding to
the returning list) for each target partition somehow. ISTM, that's going
to have to be done within the planner by appropriate inheritance
translation of the original returning targetlist.

Thanks,
Amit

Attachments:

0001-Allocate-partition_tuple_slot-in-respective-nodes.patchtext/x-diff; name=0001-Allocate-partition_tuple_slot-in-respective-nodes.patchDownload
From 89f8740195189cc77391bdb844f5092c0440f061 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 11:53:19 +0900
Subject: [PATCH 1/7] Allocate partition_tuple_slot in respective nodes

...instead of making it part of EState and its tuple table.
Respective nodes means ModifyTableState and CopyState for now.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 30 +++++++++++++++++-------------
 src/backend/executor/execMain.c        | 12 ++++++++++++
 src/backend/executor/nodeModifyTable.c | 17 ++++++++---------
 src/include/executor/executor.h        |  1 +
 src/include/nodes/execnodes.h          |  6 +++---
 5 files changed, 41 insertions(+), 25 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index aa25a23336..e5a0f1bf80 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,11 +161,18 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+
 	PartitionDispatch *partition_dispatch_info;
-	int			num_dispatch;
-	int			num_partitions;
-	ResultRelInfo *partitions;
+									/* Tuple-routing support info */
+	int			num_dispatch;		/* Number of entries in the above array */
+	int			num_partitions;		/* Number of members in the following
+									 * arrays */
+	ResultRelInfo  *partitions;		/* Per partition result relation */
 	TupleConversionMap **partition_tupconv_maps;
+									/* Per partition tuple conversion map */
+	TupleTableSlot *partition_tuple_slot;
+									/* Slot used to manipulate a tuple after
+									 * it is routed to a partition */
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1409,6 +1416,7 @@ BeginCopy(ParseState *pstate,
 			PartitionDispatch  *partition_dispatch_info;
 			ResultRelInfo	   *partitions;
 			TupleConversionMap **partition_tupconv_maps;
+			TupleTableSlot	   *partition_tuple_slot;
 			int					num_parted,
 								num_partitions;
 
@@ -1416,12 +1424,14 @@ BeginCopy(ParseState *pstate,
 										   &partition_dispatch_info,
 										   &partitions,
 										   &partition_tupconv_maps,
+										   &partition_tuple_slot,
 										   &num_parted, &num_partitions);
 			cstate->partition_dispatch_info = partition_dispatch_info;
 			cstate->num_dispatch = num_parted;
 			cstate->partitions = partitions;
 			cstate->num_partitions = num_partitions;
 			cstate->partition_tupconv_maps = partition_tupconv_maps;
+			cstate->partition_tuple_slot = partition_tuple_slot;
 		}
 	}
 	else
@@ -2436,15 +2446,6 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
-	 * Initialize a dedicated slot to manipulate tuples of any given
-	 * partition's rowtype.
-	 */
-	if (cstate->partition_dispatch_info)
-		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
-	else
-		estate->es_partition_tuple_slot = NULL;
-
-	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2591,7 +2592,7 @@ CopyFrom(CopyState cstate)
 				 * we're finished dealing with the partition.
 				 */
 				oldslot = slot;
-				slot = estate->es_partition_tuple_slot;
+				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
@@ -2756,6 +2757,9 @@ CopyFrom(CopyState cstate)
 			ExecCloseIndices(resultRelInfo);
 			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
 		}
+
+		/* Release the standalone partition tuple descriptor */
+		ExecDropSingleTupleTableSlot(cstate->partition_tuple_slot);
 	}
 
 	FreeExecutorState(estate);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..97c729d6b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3012,6 +3012,9 @@ EvalPlanQualEnd(EPQState *epqstate)
  *		entry for every leaf partition (required to convert input tuple based
  *		on the root table's rowtype to a leaf partition's rowtype after tuple
  *		routing is done
+ * 'partition_tuple_slot' receives a standalone TupleTableSlot to be used
+ *		to manipulate any given leaf partition's rowtype after that partition
+ *		is chosen by tuple-routing.
  * 'num_parted' receives the number of partitioned tables in the partition
  *		tree (= the number of entries in the 'pd' output array)
  * 'num_partitions' receives the number of leaf partitions in the partition
@@ -3026,6 +3029,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
+							   TupleTableSlot **partition_tuple_slot,
 							   int *num_parted, int *num_partitions)
 {
 	TupleDesc	tupDesc = RelationGetDescr(rel);
@@ -3043,6 +3047,14 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
 										   sizeof(TupleConversionMap *));
 
+	/*
+	 * Initialize an empty slot that will be used to manipulate tuples of any
+	 * given partition's rowtype.  It is attached to the caller-specified node
+	 * (such as ModifyTableState) and released when the node finishes
+	 * processing.
+	 */
+	*partition_tuple_slot = MakeTupleTableSlot();
+
 	leaf_part_rri = *partitions;
 	i = 0;
 	foreach(cell, leaf_parts)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0d85b151c2..df21f66df8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -329,7 +329,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * Use the dedicated slot for that.
 			 */
 			oldslot = slot;
-			slot = estate->es_partition_tuple_slot;
+			slot = mtstate->mt_partition_tuple_slot;
 			Assert(slot != NULL);
 			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
@@ -1738,6 +1738,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		PartitionDispatch  *partition_dispatch_info;
 		ResultRelInfo	   *partitions;
 		TupleConversionMap **partition_tupconv_maps;
+		TupleTableSlot	   *partition_tuple_slot;
 		int					num_parted,
 							num_partitions;
 
@@ -1745,21 +1746,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									   &partition_dispatch_info,
 									   &partitions,
 									   &partition_tupconv_maps,
+									   &partition_tuple_slot,
 									   &num_parted, &num_partitions);
 		mtstate->mt_partition_dispatch_info = partition_dispatch_info;
 		mtstate->mt_num_dispatch = num_parted;
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
-
-		/*
-		 * Initialize a dedicated slot to manipulate tuples of any given
-		 * partition's rowtype.
-		 */
-		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+		mtstate->mt_partition_tuple_slot = partition_tuple_slot;
 	}
-	else
-		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
@@ -2100,6 +2095,10 @@ ExecEndModifyTable(ModifyTableState *node)
 		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
 	}
 
+	/* Release the standalone partition tuple descriptor, if any */
+	if (node->mt_partition_tuple_slot)
+		ExecDropSingleTupleTableSlot(node->mt_partition_tuple_slot);
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5eb5d..c217bd30cb 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -217,6 +217,7 @@ extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
+							   TupleTableSlot **partition_tuple_slot,
 							   int *num_parted, int *num_partitions);
 extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d43ec56a2b..3624660861 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -384,9 +384,6 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
-	/* Slot used to manipulate a tuple after it is routed to a partition */
-	TupleTableSlot *es_partition_tuple_slot;
-
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
@@ -1165,6 +1162,9 @@ typedef struct ModifyTableState
 	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
 	TupleConversionMap **mt_partition_tupconv_maps;
 									/* Per partition tuple conversion map */
+	TupleTableSlot *mt_partition_tuple_slot;
+									/* Slot used to manipulate a tuple after
+									 * it is routed to a partition */
 } ModifyTableState;
 
 /* ----------------
-- 
2.11.0

0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchtext/x-diff; name=0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchDownload
From 41d3c159e9ec1770900638469c8354ff739af025 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 18:00:47 +0900
Subject: [PATCH 2/7] Make ExecConstraints() show the correct row in error msgs

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root table's
(ie, the table into which the row is inserted in a given query) rowtype
and the partition's.

To fix, also pass the original slot to ExecConstraints and use it to
build the val_desc to be shown in the messages.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 11 ++----
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        | 67 +++++++++++++++++++++++++++++-----
 src/backend/executor/nodeModifyTable.c | 15 +++-----
 src/include/executor/executor.h        |  4 +-
 src/include/nodes/execnodes.h          |  1 +
 src/test/regress/expected/insert.out   |  7 ++++
 src/test/regress/sql/insert.sql        |  6 +++
 8 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e5a0f1bf80..afbfb9f6e8 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2430,6 +2430,7 @@ CopyFrom(CopyState cstate)
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
 					  true,		/* do load partition check expression */
+					  NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2495,7 +2496,7 @@ CopyFrom(CopyState cstate)
 	for (;;)
 	{
 		TupleTableSlot *slot,
-					   *oldslot = NULL;
+					   *oldslot;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2537,6 +2538,7 @@ CopyFrom(CopyState cstate)
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
 		/* Determine the partition to heap_insert the tuple into */
+		oldslot = slot;
 		if (cstate->partition_dispatch_info)
 		{
 			int			leaf_part_index;
@@ -2591,7 +2593,6 @@ CopyFrom(CopyState cstate)
 				 * point on.  Use a dedicated slot from this point on until
 				 * we're finished dealing with the partition.
 				 */
-				oldslot = slot;
 				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
@@ -2628,7 +2629,7 @@ CopyFrom(CopyState cstate)
 				/* Check the constraints of the tuple */
 				if (cstate->rel->rd_att->constr ||
 					resultRelInfo->ri_PartitionCheck)
-					ExecConstraints(resultRelInfo, slot, estate);
+					ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
 				if (useHeapMultiInsert)
 				{
@@ -2690,10 +2691,6 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
-
-				/* Switch back to the slot corresponding to the root table */
-				Assert(oldslot != NULL);
-				slot = oldslot;
 			}
 		}
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a7ac85e7ab..c03edea18d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,6 +1324,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 						  rel,
 						  0,	/* dummy rangetable index */
 						  false,
+						  NULL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 97c729d6b7..32c8f28beb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,6 +828,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 							  resultRelation,
 							  resultRelationIndex,
 							  true,
+							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1218,6 +1219,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1259,6 +1261,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		resultRelInfo->ri_PartitionCheck =
 							RelationGetPartitionQual(resultRelationDesc,
 													 true);
+	/*
+	 * The following gets set to NULL unless we are initializing leaf
+	 * partitions for tuple-routing.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
 /*
@@ -1322,6 +1329,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 					  rel,
 					  0,		/* dummy rangetable index */
 					  true,
+					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1743,9 +1751,21 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
 }
 
+/*
+ * ExecConstraints - check constraints of the tuple in 'slot'
+ *
+ * This checks the traditional NOT NULL and check constraints, as well as
+ * the partition constraint, if any.
+ *
+ * Note: 'slot' contains the tuple to check the constraints of, which may
+ * have been converted from the original input tuple after tuple routing,
+ * while 'orig_slot' contains the original tuple to be shown in the message,
+ * if an error occurs.
+ */
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate)
+				TupleTableSlot *slot, TupleTableSlot *orig_slot,
+				EState *estate)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -1767,12 +1787,24 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * choose the correct relation to build val_desc from the
+				 * tuple contained in orig_slot
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 				modifiedCols = bms_union(insertedCols, updatedCols);
 				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-														 slot,
+														 orig_slot,
 														 tupdesc,
 														 modifiedCols,
 														 64);
@@ -1780,9 +1812,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1794,21 +1826,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 			modifiedCols = bms_union(insertedCols, updatedCols);
 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-													 slot,
+													 orig_slot,
 													 tupdesc,
 													 modifiedCols,
 													 64);
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1816,19 +1856,27 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 		modifiedCols = bms_union(insertedCols, updatedCols);
 		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-												 slot,
+												 orig_slot,
 												 tupdesc,
 												 modifiedCols,
 												 64);
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3086,6 +3134,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 						  partrel,
 						  1,	 /* dummy */
 						  false,
+						  rel,
 						  0);
 
 		/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index df21f66df8..825a15f42d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = NULL;
+	TupleTableSlot *oldslot = slot;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -328,7 +328,6 @@ ExecInsert(ModifyTableState *mtstate,
 			 * point on, until we're finished dealing with the partition.
 			 * Use the dedicated slot for that.
 			 */
-			oldslot = slot;
 			slot = mtstate->mt_partition_tuple_slot;
 			Assert(slot != NULL);
 			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
@@ -434,7 +433,7 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Check the constraints of the tuple
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+			ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
@@ -579,10 +578,6 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
-
-		/* Switch back to the slot corresponding to the root table */
-		Assert(oldslot != NULL);
-		slot = oldslot;
 	}
 
 	/*
@@ -994,10 +989,12 @@ lreplace:;
 								 resultRelInfo, slot, estate);
 
 		/*
-		 * Check the constraints of the tuple
+		 * Check the constraints of the tuple.  Note that we pass the same
+		 * slot for the orig_slot argument, because unlike ExecInsert(), no
+		 * tuple-routing is performed here, hence the slot remains unchanged.
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+			ExecConstraints(resultRelInfo, slot, slot, estate);
 
 		/*
 		 * replace the heap tuple
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c217bd30cb..70ecf108a3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -190,11 +190,13 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate);
+				TupleTableSlot *slot, TupleTableSlot *orig_slot,
+				EState *estate);
 extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 					 TupleTableSlot *slot, EState *estate);
 extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3624660861..633c5cc107 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,7 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 49f667b119..b120954997 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -332,6 +332,13 @@ select tableoid::regclass, * from p;
  p11      | 1 | 2
 (1 row)
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+ERROR:  new row for relation "p11" violates check constraint "check_b"
+DETAIL:  Failing row contains (1, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 08dc068de8..3d2fdb92c5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -194,5 +194,11 @@ alter table p attach partition p1 for values from (1, 2) to (1, 10);
 insert into p values (1, 2);
 select tableoid::regclass, * from p;
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchtext/x-diff; name=0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchDownload
From 4ba765e88b2a3569690ded7277edb276c1200f8f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 3/7] Fix a bug in how we generate partition constraints

Firstly, since we always want to recurse when calling
RelationGetPartitionQual(), ie, consider the parent's partition
constraint (if any), get rid of the argument recurse; also in the
module-local generate_partition_qual() that it calls.

Move the code for doing parent attnos to child attnos mapping for Vars
in partition constraint expressions to a separate function
map_partition_varattnos() and call it from the appropriate places.
Doing it in get_qual_from_partbound(), as is now, would produce wrong
result in certain multi-level partitioning cases, because it only
considers the current pair of parent-child relations.  In certain
multi-level partitioning cases, attnums for the same key attribute(s)
might differ between different pairs of consecutive levels causing the
same attribute to be numbered differently in different Vars of the same
expression tree.  Remember that we apply the whole partition constraint
(list of constraints of partitions at various levels) to a single (leaf
partition) relation.

With this commit, in generate_partition_qual(), we first generate the
the whole partition constraint (considering all levels of partitioning)
and then do the mapping from the root parent attnums to leaf partition
attnums.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c           | 103 ++++++++++++++++--------------
 src/backend/commands/tablecmds.c          |   9 ++-
 src/backend/executor/execMain.c           |   4 +-
 src/backend/optimizer/util/plancat.c      |   2 +-
 src/include/catalog/partition.h           |   3 +-
 src/test/regress/expected/alter_table.out |  30 +++++++++
 src/test/regress/sql/alter_table.sql      |  25 ++++++++
 7 files changed, 122 insertions(+), 54 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fca874752f..34ab812b44 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,51 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	if (expr == NIL)
+		return NIL;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +923,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /*
@@ -1480,7 +1489,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1494,6 +1503,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1501,20 +1514,18 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
 	/* Get pg_class.relpartbound */
-	if (!rel->rd_rel->relispartition)	/* should not happen */
-		elog(ERROR, "relation \"%s\" has relispartition = false",
-			 RelationGetRelationName(rel));
 	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
 	boundDatum = SysCacheGetAttr(RELOID, tuple,
 								 Anum_pg_class_relpartbound,
@@ -1527,18 +1538,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* this rel's partition qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c03edea18d..3c08551d38 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13325,6 +13325,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13347,8 +13348,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32c8f28beb..6a82c18571 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1259,8 +1259,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
 	 * The following gets set to NULL unless we are initializing leaf
 	 * partitions for tuple-routing.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index bf38df5d29..78220d6ac6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 62e18961d3..0a1d1db54e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3317,3 +3317,33 @@ drop cascades to table part_2
 drop cascades to table part_5
 drop cascades to table part_5_a
 drop cascades to table part_1
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+alter table p1 attach partition p11 for values from (2) to (5);
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+ERROR:  partition constraint is violated by some row
+-- cleanup
+drop table p, p1 cascade;
+NOTICE:  drop cascades to table p11
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b285a406d9..ce7e85b6ad 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,3 +2169,28 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+
+alter table p1 attach partition p11 for values from (2) to (5);
+
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+-- cleanup
+drop table p, p1 cascade;
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From b8269426ef2120ba9d5f981839e958871d9af901 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 41 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index afbfb9f6e8..5aa4449e3e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2429,7 +2429,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3c08551d38..2e51124eb3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 6a82c18571..c991a18ce8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,14 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
 
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1328,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3133,7 +3157,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 70ecf108a3..4fed4e101f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b120954997..6a6aac5a88 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -339,6 +339,12 @@ alter table p add constraint check_b check (b = 3);
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 2).
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3d2fdb92c5..171196df6d 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -200,5 +200,10 @@ alter table p add constraint check_b check (b = 3);
 -- after "(1, 2)" is routed to it
 insert into p values (1, 2);
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0005-Add-some-more-tests-for-tuple-routing.patchtext/x-diff; name=0005-Add-some-more-tests-for-tuple-routing.patchDownload
From 10b23c7759111091173380a4806f75e38ec4b805 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 21 Dec 2016 10:51:32 +0900
Subject: [PATCH 5/7] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 38 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 18 +++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6a6aac5a88..ed0513d2ff 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,34 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3_1 | Ff |    20 |    24
+ part_ee_ff3_2 | Ff |    25 |    29
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(9 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -293,13 +321,21 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff3_1
+drop cascades to table part_ee_ff3_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 171196df6d..dca89b9286 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,24 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From ca228186ae6943099ef2f421d90a9097a2356357 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 6/7] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 8 ++++++--
 src/backend/catalog/partition.c        | 5 ++---
 src/backend/commands/analyze.c         | 1 +
 src/backend/executor/execMain.c        | 1 +
 src/backend/executor/execQual.c        | 2 +-
 src/include/access/tupconvert.h        | 1 +
 6 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4787d4ca98..9cda7cbd15 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -202,6 +202,7 @@ convert_tuples_by_position(TupleDesc indesc,
 TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg)
 {
 	TupleConversionMap *map;
@@ -259,11 +260,14 @@ convert_tuples_by_name(TupleDesc indesc,
 	/*
 	 * Check to see if the map is one-to-one and the tuple types are the same.
 	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * to inject the right OID into the tuple datum.  In the partitioning
+	 * case (!consider_typeid), tdhasoids must always match between indesc
+	 * and outdesc, so we need not require tdtypeid's to be the same.)
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		(!consider_typeid || indesc->tdtypeid == outdesc->tdtypeid))
 	{
+		Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);
 		same = true;
 		for (i = 0; i < n; i++)
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 34ab812b44..23eaaf062f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1052,7 +1052,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			 */
 			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
 			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
-												   tupdesc,
+												   tupdesc, false,
 								gettext_noop("could not convert row type"));
 		}
 		else
@@ -1664,12 +1664,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f4afcd9aae..3042406b8a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1419,6 +1419,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 					map = convert_tuples_by_name(RelationGetDescr(childrel),
 												 RelationGetDescr(onerel),
+												 true,
 								 gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c991a18ce8..dfa8bdff92 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3152,6 +3152,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		 * partition from the parent's type to the partition's.
 		 */
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+													 false,
 								 gettext_noop("could not convert row type"));
 
 		InitResultRelInfo(leaf_part_rri,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index ec1ca01c5a..65d72d5fa6 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2928,7 +2928,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 
 		/* prepare map from old to new attribute numbers */
 		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
+											 cstate->outdesc, true,
 								 gettext_noop("could not convert row type"));
 		cstate->initialized = true;
 
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index 10556eec7e..2478e98160 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -36,6 +36,7 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg);
 
 extern HeapTuple do_convert_tuple(HeapTuple tuple, TupleConversionMap *map);
-- 
2.11.0

0007-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchtext/x-diff; name=0007-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchDownload
From d36a4803e1a3c79f208906c8826aa679a5c6f0cf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 16:49:06 +0900
Subject: [PATCH 7/7] Fix RETURNING to work correctly after tuple-routing

Restore the original tuple slot to be used by ExecProcessReturning().
Also, set the original slot's tuple's t_tableOid so that requesting it
in the RETURNING list works (instead of retuning 0 for the same as
happens now).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/executor/nodeModifyTable.c | 7 +++++++
 src/test/regress/expected/insert.out   | 7 +++++++
 src/test/regress/sql/insert.sql        | 3 +++
 3 files changed, 17 insertions(+)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 825a15f42d..97f03737f5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -274,6 +274,7 @@ ExecInsert(ModifyTableState *mtstate,
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
+	tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 
 	/* Determine the partition to heap_insert the tuple into */
 	if (mtstate->mt_partition_dispatch_info)
@@ -578,6 +579,12 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
+
+		/*
+		 * Switch back to the original slot for the following steps to work
+		 * sanely.
+		 */
+		slot = oldslot;
 	}
 
 	/*
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ed0513d2ff..3fa9315826 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -381,6 +381,13 @@ DETAIL:  Failing row contains (1, 2).
 insert into p1 (a, b) values (2, 3);
 ERROR:  new row for relation "p11" violates partition constraint
 DETAIL:  Failing row contains (3, 2).
+-- check that RETURNING works correctly
+insert into p values (1, 3) returning tableoid::regclass, *;
+ tableoid | a | b 
+----------+---+---
+ p        | 1 | 3
+(1 row)
+
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index dca89b9286..6831bfe0d5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -223,5 +223,8 @@ insert into p values (1, 2);
 -- selected by tuple-routing
 insert into p1 (a, b) values (2, 3);
 
+-- check that RETURNING works correctly
+insert into p values (1, 3) returning tableoid::regclass, *;
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

#238Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#237)
7 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/26 19:46, Amit Langote wrote:

(Perhaps, the following should be its own new thread)

I noticed that ExecProcessReturning() doesn't work properly after tuple
routing (example shows how returning tableoid currently fails but I
mention some other issues below):

create table p (a int, b int) partition by range (a);
create table p1 partition of p for values from (1) to (10);
insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
- | 1 |
(1 row)

INSERT 0 1

I tried to fix that in 0007 to get:

insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
p | 1 |
(1 row)

INSERT 0 1

But I think it *may* be wrong to return the root table OID for tuples
inserted into leaf partitions, because with select we get partition OIDs:

select tableoid::regclass, * from p;
tableoid | a | b
----------+---+---
p1 | 1 |
(1 row)

If so, that means we should build the projection info (corresponding to
the returning list) for each target partition somehow. ISTM, that's going
to have to be done within the planner by appropriate inheritance
translation of the original returning targetlist.

Turns out getting the 2nd result may not require planner tweaks after all.
Unless I'm missing something, translation of varattnos of the RETURNING
target list can be done as late as ExecInitModifyTable() for the insert
case, unlike update/delete (which do require planner's attention).

I updated the patch 0007 to implement the same, including the test. While
doing that, I realized map_partition_varattnos introduced in 0003 is
rather restrictive in its applicability, because it assumes varno = 1 for
the expressions it accepts as input for the mapping. Mapping returning
(target) list required modifying map_partition_varattnos to accept
target_varno as additional argument. That way, we can map arbitrary
expressions from the parent attributes numbers to partition attribute
numbers for expressions not limited to partition constraints.

Patches 0001 to 0006 unchanged.

Thanks,
Amit

Attachments:

0001-Allocate-partition_tuple_slot-in-respective-nodes.patchtext/x-diff; name=0001-Allocate-partition_tuple_slot-in-respective-nodes.patchDownload
From fcfe08948d31802547e93ac6551873afd554bc36 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 11:53:19 +0900
Subject: [PATCH 1/7] Allocate partition_tuple_slot in respective nodes

...instead of making it part of EState and its tuple table.
Respective nodes means ModifyTableState and CopyState for now.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 30 +++++++++++++++++-------------
 src/backend/executor/execMain.c        | 12 ++++++++++++
 src/backend/executor/nodeModifyTable.c | 17 ++++++++---------
 src/include/executor/executor.h        |  1 +
 src/include/nodes/execnodes.h          |  6 +++---
 5 files changed, 41 insertions(+), 25 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index aa25a23336..e5a0f1bf80 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,11 +161,18 @@ typedef struct CopyStateData
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;		/* is any of defexprs volatile? */
 	List	   *range_table;
+
 	PartitionDispatch *partition_dispatch_info;
-	int			num_dispatch;
-	int			num_partitions;
-	ResultRelInfo *partitions;
+									/* Tuple-routing support info */
+	int			num_dispatch;		/* Number of entries in the above array */
+	int			num_partitions;		/* Number of members in the following
+									 * arrays */
+	ResultRelInfo  *partitions;		/* Per partition result relation */
 	TupleConversionMap **partition_tupconv_maps;
+									/* Per partition tuple conversion map */
+	TupleTableSlot *partition_tuple_slot;
+									/* Slot used to manipulate a tuple after
+									 * it is routed to a partition */
 
 	/*
 	 * These variables are used to reduce overhead in textual COPY FROM.
@@ -1409,6 +1416,7 @@ BeginCopy(ParseState *pstate,
 			PartitionDispatch  *partition_dispatch_info;
 			ResultRelInfo	   *partitions;
 			TupleConversionMap **partition_tupconv_maps;
+			TupleTableSlot	   *partition_tuple_slot;
 			int					num_parted,
 								num_partitions;
 
@@ -1416,12 +1424,14 @@ BeginCopy(ParseState *pstate,
 										   &partition_dispatch_info,
 										   &partitions,
 										   &partition_tupconv_maps,
+										   &partition_tuple_slot,
 										   &num_parted, &num_partitions);
 			cstate->partition_dispatch_info = partition_dispatch_info;
 			cstate->num_dispatch = num_parted;
 			cstate->partitions = partitions;
 			cstate->num_partitions = num_partitions;
 			cstate->partition_tupconv_maps = partition_tupconv_maps;
+			cstate->partition_tuple_slot = partition_tuple_slot;
 		}
 	}
 	else
@@ -2436,15 +2446,6 @@ CopyFrom(CopyState cstate)
 	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
 	/*
-	 * Initialize a dedicated slot to manipulate tuples of any given
-	 * partition's rowtype.
-	 */
-	if (cstate->partition_dispatch_info)
-		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
-	else
-		estate->es_partition_tuple_slot = NULL;
-
-	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
 	 * separately for every tuple. However, we can't do that if there are
@@ -2591,7 +2592,7 @@ CopyFrom(CopyState cstate)
 				 * we're finished dealing with the partition.
 				 */
 				oldslot = slot;
-				slot = estate->es_partition_tuple_slot;
+				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
@@ -2756,6 +2757,9 @@ CopyFrom(CopyState cstate)
 			ExecCloseIndices(resultRelInfo);
 			heap_close(resultRelInfo->ri_RelationDesc, NoLock);
 		}
+
+		/* Release the standalone partition tuple descriptor */
+		ExecDropSingleTupleTableSlot(cstate->partition_tuple_slot);
 	}
 
 	FreeExecutorState(estate);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..97c729d6b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3012,6 +3012,9 @@ EvalPlanQualEnd(EPQState *epqstate)
  *		entry for every leaf partition (required to convert input tuple based
  *		on the root table's rowtype to a leaf partition's rowtype after tuple
  *		routing is done
+ * 'partition_tuple_slot' receives a standalone TupleTableSlot to be used
+ *		to manipulate any given leaf partition's rowtype after that partition
+ *		is chosen by tuple-routing.
  * 'num_parted' receives the number of partitioned tables in the partition
  *		tree (= the number of entries in the 'pd' output array)
  * 'num_partitions' receives the number of leaf partitions in the partition
@@ -3026,6 +3029,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
+							   TupleTableSlot **partition_tuple_slot,
 							   int *num_parted, int *num_partitions)
 {
 	TupleDesc	tupDesc = RelationGetDescr(rel);
@@ -3043,6 +3047,14 @@ ExecSetupPartitionTupleRouting(Relation rel,
 	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
 										   sizeof(TupleConversionMap *));
 
+	/*
+	 * Initialize an empty slot that will be used to manipulate tuples of any
+	 * given partition's rowtype.  It is attached to the caller-specified node
+	 * (such as ModifyTableState) and released when the node finishes
+	 * processing.
+	 */
+	*partition_tuple_slot = MakeTupleTableSlot();
+
 	leaf_part_rri = *partitions;
 	i = 0;
 	foreach(cell, leaf_parts)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0d85b151c2..df21f66df8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -329,7 +329,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * Use the dedicated slot for that.
 			 */
 			oldslot = slot;
-			slot = estate->es_partition_tuple_slot;
+			slot = mtstate->mt_partition_tuple_slot;
 			Assert(slot != NULL);
 			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
 			ExecStoreTuple(tuple, slot, InvalidBuffer, true);
@@ -1738,6 +1738,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		PartitionDispatch  *partition_dispatch_info;
 		ResultRelInfo	   *partitions;
 		TupleConversionMap **partition_tupconv_maps;
+		TupleTableSlot	   *partition_tuple_slot;
 		int					num_parted,
 							num_partitions;
 
@@ -1745,21 +1746,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									   &partition_dispatch_info,
 									   &partitions,
 									   &partition_tupconv_maps,
+									   &partition_tuple_slot,
 									   &num_parted, &num_partitions);
 		mtstate->mt_partition_dispatch_info = partition_dispatch_info;
 		mtstate->mt_num_dispatch = num_parted;
 		mtstate->mt_partitions = partitions;
 		mtstate->mt_num_partitions = num_partitions;
 		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
-
-		/*
-		 * Initialize a dedicated slot to manipulate tuples of any given
-		 * partition's rowtype.
-		 */
-		estate->es_partition_tuple_slot = ExecInitExtraTupleSlot(estate);
+		mtstate->mt_partition_tuple_slot = partition_tuple_slot;
 	}
-	else
-		estate->es_partition_tuple_slot = NULL;
 
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
@@ -2100,6 +2095,10 @@ ExecEndModifyTable(ModifyTableState *node)
 		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
 	}
 
+	/* Release the standalone partition tuple descriptor, if any */
+	if (node->mt_partition_tuple_slot)
+		ExecDropSingleTupleTableSlot(node->mt_partition_tuple_slot);
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b74fa5eb5d..c217bd30cb 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -217,6 +217,7 @@ extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
+							   TupleTableSlot **partition_tuple_slot,
 							   int *num_parted, int *num_partitions);
 extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
 				  PartitionDispatch *pd,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d43ec56a2b..3624660861 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -384,9 +384,6 @@ typedef struct EState
 	TupleTableSlot *es_trig_oldtup_slot;		/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;		/* for TriggerEnabled */
 
-	/* Slot used to manipulate a tuple after it is routed to a partition */
-	TupleTableSlot *es_partition_tuple_slot;
-
 	/* Parameter info: */
 	ParamListInfo es_param_list_info;	/* values of external params */
 	ParamExecData *es_param_exec_vals;	/* values of internal params */
@@ -1165,6 +1162,9 @@ typedef struct ModifyTableState
 	ResultRelInfo  *mt_partitions;	/* Per partition result relation */
 	TupleConversionMap **mt_partition_tupconv_maps;
 									/* Per partition tuple conversion map */
+	TupleTableSlot *mt_partition_tuple_slot;
+									/* Slot used to manipulate a tuple after
+									 * it is routed to a partition */
 } ModifyTableState;
 
 /* ----------------
-- 
2.11.0

0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchtext/x-diff; name=0002-Make-ExecConstraints-show-the-correct-row-in-error-m.patchDownload
From 0c46b6fcfc2e1d28a6fc4967befb5437214c658a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 18:00:47 +0900
Subject: [PATCH 2/7] Make ExecConstraints() show the correct row in error msgs

After a tuple is routed to a partition, it has been converted from the
root table's rowtype to the partition's.  If such a tuple causes an
error in ExecConstraints(), the row shown in error messages might not
match the input row due to possible differences between the root table's
(ie, the table into which the row is inserted in a given query) rowtype
and the partition's.

To fix, also pass the original slot to ExecConstraints and use it to
build the val_desc to be shown in the messages.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c            | 11 ++----
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        | 67 +++++++++++++++++++++++++++++-----
 src/backend/executor/nodeModifyTable.c | 15 +++-----
 src/include/executor/executor.h        |  4 +-
 src/include/nodes/execnodes.h          |  1 +
 src/test/regress/expected/insert.out   |  7 ++++
 src/test/regress/sql/insert.sql        |  6 +++
 8 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e5a0f1bf80..afbfb9f6e8 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2430,6 +2430,7 @@ CopyFrom(CopyState cstate)
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
 					  true,		/* do load partition check expression */
+					  NULL,
 					  0);
 
 	ExecOpenIndices(resultRelInfo, false);
@@ -2495,7 +2496,7 @@ CopyFrom(CopyState cstate)
 	for (;;)
 	{
 		TupleTableSlot *slot,
-					   *oldslot = NULL;
+					   *oldslot;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2537,6 +2538,7 @@ CopyFrom(CopyState cstate)
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
 		/* Determine the partition to heap_insert the tuple into */
+		oldslot = slot;
 		if (cstate->partition_dispatch_info)
 		{
 			int			leaf_part_index;
@@ -2591,7 +2593,6 @@ CopyFrom(CopyState cstate)
 				 * point on.  Use a dedicated slot from this point on until
 				 * we're finished dealing with the partition.
 				 */
-				oldslot = slot;
 				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
@@ -2628,7 +2629,7 @@ CopyFrom(CopyState cstate)
 				/* Check the constraints of the tuple */
 				if (cstate->rel->rd_att->constr ||
 					resultRelInfo->ri_PartitionCheck)
-					ExecConstraints(resultRelInfo, slot, estate);
+					ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
 				if (useHeapMultiInsert)
 				{
@@ -2690,10 +2691,6 @@ CopyFrom(CopyState cstate)
 			{
 				resultRelInfo = saved_resultRelInfo;
 				estate->es_result_relation_info = resultRelInfo;
-
-				/* Switch back to the slot corresponding to the root table */
-				Assert(oldslot != NULL);
-				slot = oldslot;
 			}
 		}
 	}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a7ac85e7ab..c03edea18d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,6 +1324,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 						  rel,
 						  0,	/* dummy rangetable index */
 						  false,
+						  NULL,
 						  0);
 		resultRelInfo++;
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 97c729d6b7..32c8f28beb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -828,6 +828,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 							  resultRelation,
 							  resultRelationIndex,
 							  true,
+							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
 		}
@@ -1218,6 +1219,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options)
 {
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1259,6 +1261,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		resultRelInfo->ri_PartitionCheck =
 							RelationGetPartitionQual(resultRelationDesc,
 													 true);
+	/*
+	 * The following gets set to NULL unless we are initializing leaf
+	 * partitions for tuple-routing.
+	 */
+	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
 /*
@@ -1322,6 +1329,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 					  rel,
 					  0,		/* dummy rangetable index */
 					  true,
+					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
 		lappend(estate->es_trig_target_relations, rInfo);
@@ -1743,9 +1751,21 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
 }
 
+/*
+ * ExecConstraints - check constraints of the tuple in 'slot'
+ *
+ * This checks the traditional NOT NULL and check constraints, as well as
+ * the partition constraint, if any.
+ *
+ * Note: 'slot' contains the tuple to check the constraints of, which may
+ * have been converted from the original input tuple after tuple routing,
+ * while 'orig_slot' contains the original tuple to be shown in the message,
+ * if an error occurs.
+ */
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate)
+				TupleTableSlot *slot, TupleTableSlot *orig_slot,
+				EState *estate)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -1767,12 +1787,24 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
+				Relation	orig_rel = rel;
+				TupleDesc	orig_tupdesc = tupdesc;
+
+				/*
+				 * choose the correct relation to build val_desc from the
+				 * tuple contained in orig_slot
+				 */
+				if (resultRelInfo->ri_PartitionRoot)
+				{
+					rel = resultRelInfo->ri_PartitionRoot;
+					tupdesc = RelationGetDescr(rel);
+				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 				modifiedCols = bms_union(insertedCols, updatedCols);
 				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-														 slot,
+														 orig_slot,
 														 tupdesc,
 														 modifiedCols,
 														 64);
@@ -1780,9 +1812,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
-							  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
+						  NameStr(orig_tupdesc->attrs[attrChk - 1]->attname)),
 						 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-						 errtablecol(rel, attrChk)));
+						 errtablecol(orig_rel, attrChk)));
 			}
 		}
 	}
@@ -1794,21 +1826,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
 			char	   *val_desc;
+			Relation	orig_rel = rel;
+
+			/* See the comment above. */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 			modifiedCols = bms_union(insertedCols, updatedCols);
 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-													 slot,
+													 orig_slot,
 													 tupdesc,
 													 modifiedCols,
 													 64);
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(rel), failed),
+							RelationGetRelationName(orig_rel), failed),
 			  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(rel, failed)));
+					 errtableconstraint(orig_rel, failed)));
 		}
 	}
 
@@ -1816,19 +1856,27 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		!ExecPartitionCheck(resultRelInfo, slot, estate))
 	{
 		char	   *val_desc;
+		Relation	orig_rel = rel;
+
+		/* See the comment above. */
+		if (resultRelInfo->ri_PartitionRoot)
+		{
+			rel = resultRelInfo->ri_PartitionRoot;
+			tupdesc = RelationGetDescr(rel);
+		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 		modifiedCols = bms_union(insertedCols, updatedCols);
 		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-												 slot,
+												 orig_slot,
 												 tupdesc,
 												 modifiedCols,
 												 64);
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("new row for relation \"%s\" violates partition constraint",
-						RelationGetRelationName(rel)),
+						RelationGetRelationName(orig_rel)),
 		  val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
 	}
 }
@@ -3086,6 +3134,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 						  partrel,
 						  1,	 /* dummy */
 						  false,
+						  rel,
 						  0);
 
 		/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index df21f66df8..825a15f42d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = NULL;
+	TupleTableSlot *oldslot = slot;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -328,7 +328,6 @@ ExecInsert(ModifyTableState *mtstate,
 			 * point on, until we're finished dealing with the partition.
 			 * Use the dedicated slot for that.
 			 */
-			oldslot = slot;
 			slot = mtstate->mt_partition_tuple_slot;
 			Assert(slot != NULL);
 			ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
@@ -434,7 +433,7 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Check the constraints of the tuple
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+			ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
@@ -579,10 +578,6 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		resultRelInfo = saved_resultRelInfo;
 		estate->es_result_relation_info = resultRelInfo;
-
-		/* Switch back to the slot corresponding to the root table */
-		Assert(oldslot != NULL);
-		slot = oldslot;
 	}
 
 	/*
@@ -994,10 +989,12 @@ lreplace:;
 								 resultRelInfo, slot, estate);
 
 		/*
-		 * Check the constraints of the tuple
+		 * Check the constraints of the tuple.  Note that we pass the same
+		 * slot for the orig_slot argument, because unlike ExecInsert(), no
+		 * tuple-routing is performed here, hence the slot remains unchanged.
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+			ExecConstraints(resultRelInfo, slot, slot, estate);
 
 		/*
 		 * replace the heap tuple
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c217bd30cb..70ecf108a3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -190,11 +190,13 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
 				  bool load_partition_check,
+				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate);
+				TupleTableSlot *slot, TupleTableSlot *orig_slot,
+				EState *estate);
 extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 					 TupleTableSlot *slot, EState *estate);
 extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3624660861..633c5cc107 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -349,6 +349,7 @@ typedef struct ResultRelInfo
 	List	   *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
 	List	   *ri_PartitionCheckExpr;
+	Relation	ri_PartitionRoot;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 49f667b119..b120954997 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -332,6 +332,13 @@ select tableoid::regclass, * from p;
  p11      | 1 | 2
 (1 row)
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+ERROR:  new row for relation "p11" violates check constraint "check_b"
+DETAIL:  Failing row contains (1, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 08dc068de8..3d2fdb92c5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -194,5 +194,11 @@ alter table p attach partition p1 for values from (1, 2) to (1, 10);
 insert into p values (1, 2);
 select tableoid::regclass, * from p;
 
+truncate p;
+alter table p add constraint check_b check (b = 3);
+-- check that correct input row is shown when constraint check_b fails on p11
+-- after "(1, 2)" is routed to it
+insert into p values (1, 2);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchtext/x-diff; name=0003-Fix-a-bug-in-how-we-generate-partition-constraints.patchDownload
From 346bde316b558e87f08d74ac118a57aa676d1059 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 3/7] Fix a bug in how we generate partition constraints

Firstly, since we always want to recurse when calling
RelationGetPartitionQual(), ie, consider the parent's partition
constraint (if any), get rid of the argument recurse; also in the
module-local generate_partition_qual() that it calls.

Move the code for doing parent attnos to child attnos mapping for Vars
in partition constraint expressions to a separate function
map_partition_varattnos() and call it from the appropriate places.
Doing it in get_qual_from_partbound(), as is now, would produce wrong
result in certain multi-level partitioning cases, because it only
considers the current pair of parent-child relations.  In certain
multi-level partitioning cases, attnums for the same key attribute(s)
might differ between different pairs of consecutive levels causing the
same attribute to be numbered differently in different Vars of the same
expression tree.  Remember that we apply the whole partition constraint
(list of constraints of partitions at various levels) to a single (leaf
partition) relation.

With this commit, in generate_partition_qual(), we first generate the
the whole partition constraint (considering all levels of partitioning)
and then do the mapping from the root parent attnums to leaf partition
attnums.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c           | 103 ++++++++++++++++--------------
 src/backend/commands/tablecmds.c          |   9 ++-
 src/backend/executor/execMain.c           |   4 +-
 src/backend/optimizer/util/plancat.c      |   2 +-
 src/include/catalog/partition.h           |   3 +-
 src/test/regress/expected/alter_table.out |  30 +++++++++
 src/test/regress/sql/alter_table.sql      |  25 ++++++++
 7 files changed, 122 insertions(+), 54 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fca874752f..34ab812b44 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -122,7 +122,7 @@ static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
-static List *generate_partition_qual(Relation rel, bool recurse);
+static List *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
@@ -850,10 +850,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -874,38 +870,51 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	if (expr == NIL)
+		return NIL;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -914,13 +923,13 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * Returns a list of partition quals
  */
 List *
-RelationGetPartitionQual(Relation rel, bool recurse)
+RelationGetPartitionQual(Relation rel)
 {
 	/* Quick exit */
 	if (!rel->rd_rel->relispartition)
 		return NIL;
 
-	return generate_partition_qual(rel, recurse);
+	return generate_partition_qual(rel);
 }
 
 /*
@@ -1480,7 +1489,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  * into cache memory.
  */
 static List *
-generate_partition_qual(Relation rel, bool recurse)
+generate_partition_qual(Relation rel)
 {
 	HeapTuple	tuple;
 	MemoryContext oldcxt;
@@ -1494,6 +1503,10 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Recursive callers may not have checked themselves */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
@@ -1501,20 +1514,18 @@ generate_partition_qual(Relation rel, bool recurse)
 	/* Quick copy */
 	if (rel->rd_partcheck)
 	{
-		if (parent->rd_rel->relispartition && recurse)
-			result = list_concat(generate_partition_qual(parent, true),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
+		result = list_concat(generate_partition_qual(parent),
+							 copyObject(rel->rd_partcheck));
 
-		heap_close(parent, AccessShareLock);
+		/* Mark Vars with correct attnos */
+		result = map_partition_varattnos(result, rel, parent);
+
+		/* Keep the parent locked until commit */
+		heap_close(parent, NoLock);
 		return result;
 	}
 
 	/* Get pg_class.relpartbound */
-	if (!rel->rd_rel->relispartition)	/* should not happen */
-		elog(ERROR, "relation \"%s\" has relispartition = false",
-			 RelationGetRelationName(rel));
 	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
 	boundDatum = SysCacheGetAttr(RELOID, tuple,
 								 Anum_pg_class_relpartbound,
@@ -1527,18 +1538,16 @@ generate_partition_qual(Relation rel, bool recurse)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
-	if (parent->rd_rel->relispartition && recurse)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent, true);
-		result = list_concat(parent_check, my_qual);
-	}
+	/* Add the parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition)
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/* Mark Vars with correct attnos */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy of *only* this rel's partition qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	rel->rd_partcheck = copyObject(my_qual);
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c03edea18d..3c08551d38 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13151,7 +13151,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 */
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
-								 RelationGetPartitionQual(rel, true));
+								 RelationGetPartitionQual(rel));
 	partConstraint = (List *) eval_const_expressions(NULL,
 													 (Node *) partConstraint);
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -13325,6 +13325,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13347,8 +13348,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32c8f28beb..6a82c18571 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1259,8 +1259,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	if (load_partition_check)
 		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc,
-													 true);
+							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
 	 * The following gets set to NULL unless we are initializing leaf
 	 * partitions for tuple-routing.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 72272d9bb7..150229ed6d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1228,7 +1228,7 @@ get_relation_constraints(PlannerInfo *root,
 	}
 
 	/* Append partition predicates, if any */
-	pcqual = RelationGetPartitionQual(relation, true);
+	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
 	{
 		/*
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index bf38df5d29..78220d6ac6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 62e18961d3..0a1d1db54e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3317,3 +3317,33 @@ drop cascades to table part_2
 drop cascades to table part_5
 drop cascades to table part_5_a
 drop cascades to table part_1
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+alter table p1 attach partition p11 for values from (2) to (5);
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+ERROR:  partition constraint is violated by some row
+-- cleanup
+drop table p, p1 cascade;
+NOTICE:  drop cascades to table p11
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b285a406d9..ce7e85b6ad 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,3 +2169,28 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+
+alter table p1 attach partition p11 for values from (2) to (5);
+
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+-- cleanup
+drop table p, p1 cascade;
-- 
2.11.0

0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0004-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 8d3a6e9474c9209ad8985998080f3d9ce3bd50b7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 4/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 41 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index afbfb9f6e8..5aa4449e3e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2429,7 +2429,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3c08551d38..2e51124eb3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1323,7 +1323,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 6a82c18571..c991a18ce8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,14 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
 
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1328,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3133,7 +3157,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 70ecf108a3..4fed4e101f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b120954997..6a6aac5a88 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -339,6 +339,12 @@ alter table p add constraint check_b check (b = 3);
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 2).
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p cascade;
 NOTICE:  drop cascades to 2 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3d2fdb92c5..171196df6d 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -200,5 +200,10 @@ alter table p add constraint check_b check (b = 3);
 -- after "(1, 2)" is routed to it
 insert into p values (1, 2);
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

0005-Add-some-more-tests-for-tuple-routing.patchtext/x-diff; name=0005-Add-some-more-tests-for-tuple-routing.patchDownload
From b9353c9b7c548cc21ec1860940ed0c6ecd919485 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 21 Dec 2016 10:51:32 +0900
Subject: [PATCH 5/7] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 38 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 18 +++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6a6aac5a88..ed0513d2ff 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,34 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3_1 | Ff |    20 |    24
+ part_ee_ff3_2 | Ff |    25 |    29
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(9 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -293,13 +321,21 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff3_1
+drop cascades to table part_ee_ff3_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 171196df6d..dca89b9286 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,24 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From 9614d04bb9505ee4c4c94fd8899b8c3578a4dbd7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 6/7] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 8 ++++++--
 src/backend/catalog/partition.c        | 5 ++---
 src/backend/commands/analyze.c         | 1 +
 src/backend/executor/execMain.c        | 1 +
 src/backend/executor/execQual.c        | 2 +-
 src/include/access/tupconvert.h        | 1 +
 6 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index 4787d4ca98..9cda7cbd15 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -202,6 +202,7 @@ convert_tuples_by_position(TupleDesc indesc,
 TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg)
 {
 	TupleConversionMap *map;
@@ -259,11 +260,14 @@ convert_tuples_by_name(TupleDesc indesc,
 	/*
 	 * Check to see if the map is one-to-one and the tuple types are the same.
 	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * to inject the right OID into the tuple datum.  In the partitioning
+	 * case (!consider_typeid), tdhasoids must always match between indesc
+	 * and outdesc, so we need not require tdtypeid's to be the same.)
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		(!consider_typeid || indesc->tdtypeid == outdesc->tdtypeid))
 	{
+		Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);
 		same = true;
 		for (i = 0; i < n; i++)
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 34ab812b44..23eaaf062f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1052,7 +1052,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			 */
 			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
 			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
-												   tupdesc,
+												   tupdesc, false,
 								gettext_noop("could not convert row type"));
 		}
 		else
@@ -1664,12 +1664,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f4afcd9aae..3042406b8a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1419,6 +1419,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 					map = convert_tuples_by_name(RelationGetDescr(childrel),
 												 RelationGetDescr(onerel),
+												 true,
 								 gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c991a18ce8..dfa8bdff92 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3152,6 +3152,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		 * partition from the parent's type to the partition's.
 		 */
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+													 false,
 								 gettext_noop("could not convert row type"));
 
 		InitResultRelInfo(leaf_part_rri,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index ec1ca01c5a..65d72d5fa6 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2928,7 +2928,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 
 		/* prepare map from old to new attribute numbers */
 		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
+											 cstate->outdesc, true,
 								 gettext_noop("could not convert row type"));
 		cstate->initialized = true;
 
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index 10556eec7e..2478e98160 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -36,6 +36,7 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg);
 
 extern HeapTuple do_convert_tuple(HeapTuple tuple, TupleConversionMap *map);
-- 
2.11.0

0007-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchtext/x-diff; name=0007-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchDownload
From 98f3be1315ca534c844287695d94efa990be11f1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 27 Dec 2016 16:56:58 +0900
Subject: [PATCH 7/7] Fix RETURNING to work correctly after tuple-routing

In ExecInsert(), do not switch back to the original resultRelInfo until
after we finish ExecProcessReturning(), so that RETURNING projection
is done considering the partition the tuple was routed to.  For the
projection to work correctly, we must initialize the same for each
leaf partition during ModifyTableState initialization.

With this commit, map_partition_varattnos() now accepts one more argument
viz. target_varno.  Previously, it assumed varno = 1 for its input
expressions, which limited its applicability.  It was enought so far
since its usage was limited to partition constraints. To use it with
expressions such as an INSERT's returning list, we must be prepared for
varnos != 1 as in the change above.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c        | 13 +++++-----
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        |  4 +--
 src/backend/executor/nodeModifyTable.c | 46 +++++++++++++++++++++++++++-------
 src/include/catalog/partition.h        |  3 ++-
 src/test/regress/expected/insert.out   | 28 ++++++++++++++++++++-
 src/test/regress/sql/insert.sql        | 14 +++++++++++
 7 files changed, 90 insertions(+), 19 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 23eaaf062f..bbc010105e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -881,7 +881,8 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * different from the parent's.
  */
 List *
-map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent)
 {
 	TupleDesc	tupdesc = RelationGetDescr(parent);
 	AttrNumber	attno;
@@ -906,7 +907,7 @@ map_partition_varattnos(List *expr, Relation partrel, Relation parent)
 	}
 
 	expr = (List *) map_variable_attnos((Node *) expr,
-										1, 0,
+										target_varno, 0,
 										part_attnos,
 										tupdesc->natts,
 										&found_whole_row);
@@ -1517,8 +1518,8 @@ generate_partition_qual(Relation rel)
 		result = list_concat(generate_partition_qual(parent),
 							 copyObject(rel->rd_partcheck));
 
-		/* Mark Vars with correct attnos */
-		result = map_partition_varattnos(result, rel, parent);
+		/* Mark Vars with correct attnos (dummy varno = 1) */
+		result = map_partition_varattnos(result, 1, rel, parent);
 
 		/* Keep the parent locked until commit */
 		heap_close(parent, NoLock);
@@ -1544,8 +1545,8 @@ generate_partition_qual(Relation rel)
 	else
 		result = my_qual;
 
-	/* Mark Vars with correct attnos */
-	result = map_partition_varattnos(result, rel, parent);
+	/* Mark Vars with correct attnos (dummy varno = 1) */
+	result = map_partition_varattnos(result, 1, rel, parent);
 
 	/* Save a copy of *only* this rel's partition qual in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2e51124eb3..caf6b36255 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13349,6 +13349,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			constr = linitial(partConstraint);
 			my_constr = make_ands_implicit((Expr *) constr);
 			tab->partition_constraint = map_partition_varattnos(my_constr,
+																1,
 																part_rel,
 																rel);
 			/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dfa8bdff92..45940706d7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1279,10 +1279,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		/*
 		 * This is not our own partition constraint, but rather an ancestor's.
 		 * So any Vars in it bear the ancestor's attribute numbers.  We must
-		 * switch them to our own.
+		 * switch them to our own. (dummy varno = 1)
 		 */
 		if (partition_check != NIL)
-			partition_check = map_partition_varattnos(partition_check,
+			partition_check = map_partition_varattnos(partition_check, 1,
 													  resultRelationDesc,
 													  partition_root);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 825a15f42d..95262114f1 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,8 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = slot;
+	TupleTableSlot *oldslot = slot,
+				   *result = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -574,12 +575,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
-	if (saved_resultRelInfo)
-	{
-		resultRelInfo = saved_resultRelInfo;
-		estate->es_result_relation_info = resultRelInfo;
-	}
-
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -597,9 +592,15 @@ ExecInsert(ModifyTableState *mtstate,
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
-		return ExecProcessReturning(resultRelInfo, slot, planSlot);
+		result = ExecProcessReturning(resultRelInfo, slot, planSlot);
 
-	return NULL;
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
+	return result;
 }
 
 /* ----------------------------------------------------------------
@@ -1786,6 +1787,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		TupleTableSlot *slot;
 		ExprContext *econtext;
+		List		*returningList;
 
 		/*
 		 * Initialize result tuple slot and assign its rowtype using the first
@@ -1818,6 +1820,32 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
+
+		/*
+		 * Build a projection for each leaf partition rel.  Note that we
+		 * didn't build the returningList for each partition within the
+		 * planner, but simple translation of the varattnos for each
+		 * partition will suffice.  This only occurs for the INSERT case;
+		 * UPDATE/DELETE are handled above.
+		 */
+		resultRelInfo = mtstate->mt_partitions;
+		returningList = linitial(node->returningLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *rlist,
+					   *rliststate;
+
+			/* varno = node->nominalRelation */
+			rlist = map_partition_varattnos(returningList,
+											node->nominalRelation,
+											partrel, rel);
+			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+			resultRelInfo->ri_projectReturning =
+				ExecBuildProjectionInfo(rliststate, econtext, slot,
+									 resultRelInfo->ri_RelationDesc->rd_att);
+			resultRelInfo++;
+		}
 	}
 	else
 	{
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 78220d6ac6..ba334328f3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ed0513d2ff..c8c47ba829 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -381,8 +381,34 @@ DETAIL:  Failing row contains (1, 2).
 insert into p1 (a, b) values (2, 3);
 ERROR:  new row for relation "p11" violates partition constraint
 DETAIL:  Failing row contains (3, 2).
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+  a  | b | min | max 
+-----+---+-----+-----
+ p11 | 1 |   2 |   4
+ p12 | 1 |   5 |   9
+ p2  | 1 |  10 |  19
+ p3  | 1 |  20 |  29
+ p4  | 1 |  30 |  39
+(5 rows)
+
 -- cleanup
 drop table p cascade;
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to table p1
 drop cascades to table p11
+drop cascades to table p12
+drop cascades to table p2
+drop cascades to table p3
+drop cascades to table p4
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index dca89b9286..2bf8cc1659 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -223,5 +223,19 @@ insert into p values (1, 2);
 -- selected by tuple-routing
 insert into p1 (a, b) values (2, 3);
 
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+
 -- cleanup
 drop table p cascade;
-- 
2.11.0

#239Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#238)
Re: Declarative partitioning - another take

Hi Amit,

I have pulled latest sources from git and tried to create multi-level
partition, getting a server crash, below are steps to reproduce. please
check if it is reproducible in your machine also.

postgres=# CREATE TABLE test_ml (a int, b int, c varchar) PARTITION BY
RANGE(a);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1 PARTITION OF test_ml FOR VALUES FROM (0)
TO (250) PARTITION BY RANGE (b);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1_p1 PARTITION OF test_ml_p1 FOR VALUES
FROM (0) TO (100);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1_p2 PARTITION OF test_ml_p1 FOR VALUES
FROM (100) TO (250);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2 PARTITION OF test_ml FOR VALUES FROM
(250) TO (500) PARTITION BY RANGE (c);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2_p1 PARTITION OF test_ml_p2 FOR VALUES
FROM ('0250') TO ('0400');
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2_p2 PARTITION OF test_ml_p2 FOR VALUES
FROM ('0400') TO ('0500');
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3 PARTITION OF test_ml FOR VALUES FROM
(500) TO (600) PARTITION BY RANGE ((b + a));
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3_p1 PARTITION OF test_ml_p3 FOR VALUES
FROM (1000) TO (1100);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3_p2 PARTITION OF test_ml_p3 FOR VALUES
FROM (1100) TO (1200);
CREATE TABLE
postgres=# INSERT INTO test_ml SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#240高增琦
pgf00a@gmail.com
In reply to: Rajkumar Raghuwanshi (#239)
Re: Declarative partitioning - another take

Hi ,

I tried "COPY FROM" in the git version. It inserts rows to wrong partition.

step to reproduce:
create table t(a int, b int) partition by range(a);
create table t_p1 partition of t for values from (1) to (100);
create table t_p2 partition of t for values from (100) to (200);
create table t_p3 partition of t for values from (200) to (300);
insert into t values(1,1);
insert into t values(101,101);
insert into t values(201,201);
copy (select * from t) to '/tmp/test2.txt';
copy t from '/tmp/test2.txt';
select * from t_p1;

result:
postgres=# select * from t_p1;
a | b
-----+-----
1 | 1
1 | 1
101 | 101
201 | 201
(4 rows)

I think the argument "BulkInsertState" used in CopyFrom/heap_insert
is related to this problem. Please check it.

Thanks.

2016-12-27 17:30 GMT+08:00 Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com>:

Hi Amit,

I have pulled latest sources from git and tried to create multi-level
partition, getting a server crash, below are steps to reproduce. please
check if it is reproducible in your machine also.

postgres=# CREATE TABLE test_ml (a int, b int, c varchar) PARTITION BY
RANGE(a);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1 PARTITION OF test_ml FOR VALUES FROM
(0) TO (250) PARTITION BY RANGE (b);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1_p1 PARTITION OF test_ml_p1 FOR VALUES
FROM (0) TO (100);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p1_p2 PARTITION OF test_ml_p1 FOR VALUES
FROM (100) TO (250);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2 PARTITION OF test_ml FOR VALUES FROM
(250) TO (500) PARTITION BY RANGE (c);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2_p1 PARTITION OF test_ml_p2 FOR VALUES
FROM ('0250') TO ('0400');
CREATE TABLE
postgres=# CREATE TABLE test_ml_p2_p2 PARTITION OF test_ml_p2 FOR VALUES
FROM ('0400') TO ('0500');
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3 PARTITION OF test_ml FOR VALUES FROM
(500) TO (600) PARTITION BY RANGE ((b + a));
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3_p1 PARTITION OF test_ml_p3 FOR VALUES
FROM (1000) TO (1100);
CREATE TABLE
postgres=# CREATE TABLE test_ml_p3_p2 PARTITION OF test_ml_p3 FOR VALUES
FROM (1100) TO (1200);
CREATE TABLE
postgres=# INSERT INTO test_ml SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

--
GaoZengqi
pgf00a@gmail.com
zengqigao@gmail.com

#241Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#239)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/27 18:30, Rajkumar Raghuwanshi wrote:

Hi Amit,

I have pulled latest sources from git and tried to create multi-level
partition, getting a server crash, below are steps to reproduce. please
check if it is reproducible in your machine also.

[ ... ]

postgres=# INSERT INTO test_ml SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks for the example. Looks like there was an oversight in my patch
that got committed as 2ac3ef7a01 [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff.

Attached patch should fix the same.

Thanks,
Amit

[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff

Attachments:

fix-wrong-ecxt_scantuple-crash.patchtext/x-diff; name=fix-wrong-ecxt_scantuple-crash.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fca874752f..f9daf8052d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1647,6 +1647,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		PartitionDesc partdesc = parent->partdesc;
 		TupleTableSlot *myslot = parent->tupslot;
 		TupleConversionMap *map = parent->tupmap;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
 
 		/* Quick exit */
 		if (partdesc->nparts == 0)
@@ -1667,6 +1668,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/* Extract partition key from tuple */
+		econtext->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..1d699c1dab 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3107,9 +3107,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
#242Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: 高增琦 (#240)
1 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/27 18:48, 高增琦 wrote:

Hi ,

I tried "COPY FROM" in the git version. It inserts rows to wrong partition.

step to reproduce:
create table t(a int, b int) partition by range(a);
create table t_p1 partition of t for values from (1) to (100);
create table t_p2 partition of t for values from (100) to (200);
create table t_p3 partition of t for values from (200) to (300);
insert into t values(1,1);
insert into t values(101,101);
insert into t values(201,201);
copy (select * from t) to '/tmp/test2.txt';
copy t from '/tmp/test2.txt';
select * from t_p1;

result:
postgres=# select * from t_p1;
a | b
-----+-----
1 | 1
1 | 1
101 | 101
201 | 201
(4 rows)

I think the argument "BulkInsertState" used in CopyFrom/heap_insert
is related to this problem. Please check it.

You're quite right. Attached should fix that.

Thanks,
Amit

Attachments:

no-BulkInsertState-partitioned-table.patchtext/x-diff; name=no-BulkInsertState-partitioned-table.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index aa25a23336..e9bf4afa44 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2290,7 +2290,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2482,7 +2482,8 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2707,7 +2708,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate != NULL)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
#243Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#242)
2 attachment(s)
Re: Declarative partitioning - another take

On 2016/12/27 19:07, Amit Langote wrote:

Attached should fix that.

Here are the last two patches with additional information like other
patches. Forgot to do that yesterday.

Thanks,
Amit

Attachments:

0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From 5a82b4caa6cec7845eb48e0397fab49c74b8dd98 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 1/2] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successive levels.  If we do end up changing the slot from
the original, we must update ecxt_scantuple to point to the new one for
partition key expressions to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 2 ++
 src/backend/executor/execMain.c      | 2 --
 src/test/regress/expected/insert.out | 2 +-
 src/test/regress/sql/insert.sql      | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fca874752f..f9daf8052d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1647,6 +1647,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		PartitionDesc partdesc = parent->partdesc;
 		TupleTableSlot *myslot = parent->tupslot;
 		TupleConversionMap *map = parent->tupmap;
+		ExprContext *econtext = GetPerTupleExprContext(estate);
 
 		/* Quick exit */
 		if (partdesc->nparts == 0)
@@ -1667,6 +1668,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/* Extract partition key from tuple */
+		econtext->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bca34a509c..1d699c1dab 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3107,9 +3107,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 49f667b119..ae54625034 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -302,7 +302,7 @@ drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 08dc068de8..9d3a34073c 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -173,7 +173,7 @@ drop table list_parted cascade;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0002-No-BulkInsertState-when-tuple-routing-is-in-action.patchtext/x-diff; name=0002-No-BulkInsertState-when-tuple-routing-is-in-action.patchDownload
From 798c51254563735dff843c71f1bbf34b969d8162 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:28:37 +0900
Subject: [PATCH 2/2] No BulkInsertState when tuple-routing is in action
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In future, we might consider alleviating this restriction by
allocating a BulkInsertState per partition.

Reported by: 高增琦 <pgf00a(at)gmail(dot)com>
Patch by: Amit Langote (with pointers from 高增琦)
Reports: https://www.postgresql.org/message-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ%3D%3DcJpgEW4vA%40mail.gmail.com
---
 src/backend/commands/copy.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index aa25a23336..e9bf4afa44 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2290,7 +2290,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2482,7 +2482,8 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	if (useHeapMultiInsert)
+		bistate = GetBulkInsertState();
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2707,7 +2708,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate != NULL)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
-- 
2.11.0

#244Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#241)
Re: Declarative partitioning - another take

On Tue, Dec 27, 2016 at 3:24 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

On 2016/12/27 18:30, Rajkumar Raghuwanshi wrote:

Hi Amit,

I have pulled latest sources from git and tried to create multi-level
partition, getting a server crash, below are steps to reproduce. please
check if it is reproducible in your machine also.

[ ... ]

postgres=# INSERT INTO test_ml SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks for the example. Looks like there was an oversight in my patch
that got committed as 2ac3ef7a01 [1].

Attached patch should fix the same.

Thanks,
Amit

[1]
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=
2ac3ef7a01df859c62d0a02333b646d65eaec5ff

Hi Amit,

I have applied attached patch, server crash for range is fixed, but still
getting crash for multi-level list partitioning insert.

postgres=# CREATE TABLE test_ml_l (a int, b int, c varchar) PARTITION BY
LIST(c);
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p1 PARTITION OF test_ml_l FOR VALUES IN
('0000', '0003', '0004', '0010') PARTITION BY LIST (c);
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p1_p1 PARTITION OF test_ml_l_p1 FOR
VALUES IN ('0000', '0003');
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p1_p2 PARTITION OF test_ml_l_p1 FOR
VALUES IN ('0004', '0010');
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p2 PARTITION OF test_ml_l FOR VALUES IN
('0001', '0005', '0002', '0009') PARTITION BY LIST (c);
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p2_p1 PARTITION OF test_ml_l_p2 FOR
VALUES IN ('0001', '0005');
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p2_p2 PARTITION OF test_ml_l_p2 FOR
VALUES IN ('0002', '0009');
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p3 PARTITION OF test_ml_l FOR VALUES IN
('0006', '0007', '0008', '0011') PARTITION BY LIST (ltrim(c,'A'));
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p3_p1 PARTITION OF test_ml_l_p3 FOR
VALUES IN ('0006', '0007');
CREATE TABLE
postgres=# CREATE TABLE test_ml_l_p3_p2 PARTITION OF test_ml_l_p3 FOR
VALUES IN ('0008', '0011');
CREATE TABLE
postgres=# INSERT INTO test_ml_l SELECT i, i, to_char(i/50, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#245Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#244)
Re: Declarative partitioning - another take

On 2017/01/03 19:04, Rajkumar Raghuwanshi wrote:

On Tue, Dec 27, 2016 at 3:24 PM, Amit Langote wrote:

Attached patch should fix the same.

I have applied attached patch, server crash for range is fixed, but still
getting crash for multi-level list partitioning insert.

postgres=# CREATE TABLE test_ml_l (a int, b int, c varchar) PARTITION BY
LIST(c);

[ ... ]

postgres=# INSERT INTO test_ml_l SELECT i, i, to_char(i/50, 'FM0000') FROM
generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Hm, that's odd. I tried your new example, but didn't get the crash.

Thanks,
Amit

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

#246Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#245)
Re: Declarative partitioning - another take

On Wed, Jan 4, 2017 at 10:37 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

On 2017/01/03 19:04, Rajkumar Raghuwanshi wrote:

On Tue, Dec 27, 2016 at 3:24 PM, Amit Langote wrote:

Attached patch should fix the same.

I have applied attached patch, server crash for range is fixed, but still
getting crash for multi-level list partitioning insert.

postgres=# CREATE TABLE test_ml_l (a int, b int, c varchar) PARTITION BY
LIST(c);

[ ... ]

postgres=# INSERT INTO test_ml_l SELECT i, i, to_char(i/50, 'FM0000')

FROM

generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Hm, that's odd. I tried your new example, but didn't get the crash.

Thanks,
Amit

Thanks, I have pulled latest sources from git, and then applied patch
"fix-wrong-ecxt_scantuple-crash.patch", Not getting crash now, may be I
have missed something last time.

#247高增琦
pgf00a@gmail.com
In reply to: Rajkumar Raghuwanshi (#246)
Re: Declarative partitioning - another take

Server crash(failed assertion) when two "insert" in one SQL:

Step to reproduce:
create table t(a int, b int) partition by range(a);
create table t_p1 partition of t for values from (1) to (100);
create table t_p2 partition of t for values from (100) to (200);
create table t_p3 partition of t for values from (200) to (300);

create table b(a int, b int);
with a(a,b) as(insert into t values(3, 3) returning a, b) insert into b
select * from a;

Please check it.

2017-01-04 14:11 GMT+08:00 Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com>:

On Wed, Jan 4, 2017 at 10:37 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/01/03 19:04, Rajkumar Raghuwanshi wrote:

On Tue, Dec 27, 2016 at 3:24 PM, Amit Langote wrote:

Attached patch should fix the same.

I have applied attached patch, server crash for range is fixed, but

still

getting crash for multi-level list partitioning insert.

postgres=# CREATE TABLE test_ml_l (a int, b int, c varchar) PARTITION BY
LIST(c);

[ ... ]

postgres=# INSERT INTO test_ml_l SELECT i, i, to_char(i/50, 'FM0000')

FROM

generate_series(0, 599, 2) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Hm, that's odd. I tried your new example, but didn't get the crash.

Thanks,
Amit

Thanks, I have pulled latest sources from git, and then applied patch
"fix-wrong-ecxt_scantuple-crash.patch", Not getting crash now, may be I
have missed something last time.

--
GaoZengqi
pgf00a@gmail.com
zengqigao@gmail.com

#248Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: 高增琦 (#247)
Re: Declarative partitioning - another take

On 2017/01/04 16:31, 高增琦 wrote:

Server crash(failed assertion) when two "insert" in one SQL:

Step to reproduce:
create table t(a int, b int) partition by range(a);
create table t_p1 partition of t for values from (1) to (100);
create table t_p2 partition of t for values from (100) to (200);
create table t_p3 partition of t for values from (200) to (300);

create table b(a int, b int);
with a(a,b) as(insert into t values(3, 3) returning a, b) insert into b
select * from a;

Please check it.

Thanks for testing! This should be fixed by a patch I posted earlier (Try
the patch 0001 of the patches posted at [1]/messages/by-id/f6f3a214-5bb5-aa8c-f82c-c720348cf086@lab.ntt.co.jp). Robert did express his
concern [2]/messages/by-id/CA+TgmoYUfs8peo-p+Stw7afTdXqNWv_S4dx_6AWc-Y_ZrGWZbQ@mail.gmail.com about the approach used in my patch that was committed as
2ac3ef7a01 [3]https://git.postgresql.org/gitweb/?p=postgresql.git&amp;a=commit&amp;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff; your test demonstrates that it wasn't a good approach
after all.

Regards,
Amit

[1]: /messages/by-id/f6f3a214-5bb5-aa8c-f82c-c720348cf086@lab.ntt.co.jp
/messages/by-id/f6f3a214-5bb5-aa8c-f82c-c720348cf086@lab.ntt.co.jp

[2]: /messages/by-id/CA+TgmoYUfs8peo-p+Stw7afTdXqNWv_S4dx_6AWc-Y_ZrGWZbQ@mail.gmail.com
/messages/by-id/CA+TgmoYUfs8peo-p+Stw7afTdXqNWv_S4dx_6AWc-Y_ZrGWZbQ@mail.gmail.com

[3]: https://git.postgresql.org/gitweb/?p=postgresql.git&amp;a=commit&amp;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff
https://git.postgresql.org/gitweb/?p=postgresql.git&amp;a=commit&amp;h=2ac3ef7a01df859c62d0a02333b646d65eaec5ff

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

#249Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#237)
Re: Declarative partitioning - another take

On Mon, Dec 26, 2016 at 5:46 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/23 8:08, Robert Haas wrote:

On Thu, Dec 22, 2016 at 3:35 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

While working on that, I discovered yet-another-bug having to do with the
tuple descriptor that's used as we route a tuple down a partition tree. If
attnums of given key attribute(s) are different on different levels, it
would be incorrect to use the original slot's (one passed by ExecInsert())
tuple descriptor to inspect the original slot's heap tuple, as we go down
the tree. It might cause spurious "partition not found" at some level due
to looking at incorrect field in the input tuple because of using the
wrong tuple descriptor (root table's attnums not always same as other
partitioned tables in the tree). Patch 0001 fixes that including a test.

I committed this, but I'm a bit uncomfortable with it: should the
TupleTableSlot be part of the ModifyTableState rather than the EState?

Done that way in 0001 of the attached patches. So, instead of making the
standalone partition_tuple_slot a field of EState (with the actual
TupleTableSlot in its tupleTable), it is now allocated within
ModifyTableState and CopyState, and released when ModifyTable node or
CopyFrom finishes, respectively.

I dropped some comments from this and committed it. They were
formatted in a way that wouldn't survive pgindent.

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

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

#250Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#243)
Re: Declarative partitioning - another take

On Tue, Dec 27, 2016 at 8:41 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/27 19:07, Amit Langote wrote:

Attached should fix that.

Here are the last two patches with additional information like other
patches. Forgot to do that yesterday.

0001 has the disadvantage that get_partition_for_tuple() acquires a
side effect. That seems undesirable. At the least, it needs to be
documented in the function's header comment.

It's unclear to me why we need to do 0002. It doesn't seem like it
should be necessary, it doesn't seem like a good idea, and the commit
message you proposed is uninformative.

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

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

#251Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#238)
Re: Declarative partitioning - another take

On Tue, Dec 27, 2016 at 3:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Patches 0001 to 0006 unchanged.

Committed 0001 earlier, as mentioned in a separate email. Committed
0002 and part of 0003. But I'm skeptical that the as-patched-by-0003
logic in generate_partition_qual() makes sense. You do this:

result = list_concat(generate_partition_qual(parent),
copyObject(rel->rd_partcheck));

/* Mark Vars with correct attnos */
result = map_partition_varattnos(result, rel, parent);

But that has the effect of applying map_partition_varattnos to
everything in rel->rd_partcheck in addition to applying it to
everything returned by generate_partition_qual() on the parent, which
doesn't seem right.

Also, don't we want to do map_partition_varattnos() just ONCE, rather
than on every call to this function? I think maybe your concern is
that the parent might be changed without a relcache flush on the
child, but I don't quite see how that could happen. If the parent's
tuple descriptor changes, surely the child's tuple descriptor would
have to be altered at the same time...

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

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

#252Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Venkata B Nagothi (#208)
Re: Declarative partitioning - another take

On 2016/12/14 12:14, Venkata B Nagothi wrote:

Loading the data into a normal table is not an issue (infact the csv is
generated from the table itself)

The issue is occurring only when i am trying to load the data from CSV file
into a partitioned table -

db01=# CREATE TABLE orders_y1992
PARTITION OF orders2 FOR VALUES FROM ('1992-01-01') TO ('1992-12-31');
CREATE TABLE
db01=# copy orders2 from '/data/orders-1993.csv' delimiter '|';
ERROR: could not read block 6060 in file "base/16384/16407": read only 0
of 8192 bytes
CONTEXT: COPY orders2, line 376589:
"9876391|374509|O|54847|1997-07-16|3-MEDIUM |Clerk#000001993|0|ithely
regular pack"

Not sure why COPY is failing.

I think I've been able to reproduce this issue and suspect that it's a
bug. I tried to solve it in response to another related report [1]/messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com, where
it was apparent that the cause was related to how the bulk-insert mode in
the COPY FROM code is not handled correctly for a partitioned table. My
proposed solution [2]/messages/by-id/101e2c2d-45d6-fb1a-468c-d3f67572a2f3@lab.ntt.co.jp was to disable bulk-insert mode completely for
partitioned tables. But it may not be desirable performance-wise (for
example, COPY FROM on partitioned tables would have same performance as
INSERT, whereas in case of regular tables, COPY FROM is much faster than
INSERT due to the bulk insert mode).

I will propose another solution for the same. Meanwhile, could you please
try your test again with the patch posted at [1]/messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com, although it will not
likely be committed as the fix for this issue.

Thanks,
Amit

[1]: /messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com
/messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com

[2]: /messages/by-id/101e2c2d-45d6-fb1a-468c-d3f67572a2f3@lab.ntt.co.jp
/messages/by-id/101e2c2d-45d6-fb1a-468c-d3f67572a2f3@lab.ntt.co.jp

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

#253Keith Fiske
keith@omniti.com
In reply to: Amit Langote (#252)
Re: Declarative partitioning - another take

Could we get some clarification on the partition_bound_spec portion of the
PARTITION OF clause? Just doing some testing it seems it's inclusive of the
FROM value but exclusive of the TO value. I don't see mention of this in
the docs as of commit 18fc5192a631441a73e6a3b911ecb14765140389 yesterday.
It does mention that the values aren't allowed to overlap, but looking at
the schema below, without the clarification of which side is
inclusive/exclusive it seems confusing because 2016-08-01 is in both. Even
the child table does not clarify this. Not sure if there's a way to do this
in the \d+ display which would be ideal, but it should at least be
mentioned in the docs.

keith@keith=# \d+ measurement
Table "public.measurement"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
-----------+---------+-----------+----------+---------+---------+--------------+-------------
logdate | date | | not null | | plain
| |
peaktemp | integer | | | 1 | plain
| |
unitsales | integer | | | | plain
| |
Partition key: RANGE (logdate)
Check constraints:
"measurement_peaktemp_check" CHECK (peaktemp > 0)
Partitions: measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO
('2016-08-01'),
measurement_y2016m08 FOR VALUES FROM ('2016-08-01') TO
('2016-09-01')

keith@keith=# \d+ measurement_y2016m07
Table "public.measurement_y2016m07"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
-----------+---------+-----------+----------+---------+---------+--------------+-------------
logdate | date | | not null | | plain
| |
peaktemp | integer | | | 1 | plain
| |
unitsales | integer | | | 0 | plain
| |
Partition of: measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
Check constraints:
"measurement_peaktemp_check" CHECK (peaktemp > 0)

keith@keith=# insert into measurement (logdate) values ('2016-08-01');
INSERT 0 1
Time: 2.848 ms

keith@keith=# select * from measurement_y2016m07;
logdate | peaktemp | unitsales
---------+----------+-----------
(0 rows)

Time: 0.273 ms
keith@keith=# select * from measurement_y2016m08;
logdate | peaktemp | unitsales
------------+----------+-----------
2016-08-01 | 1 | «NULL»
(1 row)

Time: 0.272 ms

keith@keith=# drop table measurement_y2016m08;
DROP TABLE
Time: 5.919 ms
keith@keith=# select * from only measurement;
logdate | peaktemp | unitsales
---------+----------+-----------
(0 rows)

Time: 0.307 ms
keith@keith=# insert into measurement (logdate) values ('2016-08-01');
ERROR: no partition of relation "measurement" found for row
DETAIL: Failing row contains (2016-08-01, 1, null).
Time: 0.622 ms

#254Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Keith Fiske (#253)
Re: Declarative partitioning - another take

Hi Keith,

On 2017/01/06 2:16, Keith Fiske wrote:

Could we get some clarification on the partition_bound_spec portion of the
PARTITION OF clause? Just doing some testing it seems it's inclusive of the
FROM value but exclusive of the TO value. I don't see mention of this in
the docs as of commit 18fc5192a631441a73e6a3b911ecb14765140389 yesterday.
It does mention that the values aren't allowed to overlap, but looking at
the schema below, without the clarification of which side is
inclusive/exclusive it seems confusing because 2016-08-01 is in both. Even
the child table does not clarify this. Not sure if there's a way to do this
in the \d+ display which would be ideal, but it should at least be
mentioned in the docs.

I agree that needs highlighting. I'm planning to write a doc patch for
that (among other documentation improvements).

Thanks,
Amit

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

#255Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#250)
3 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/05 3:26, Robert Haas wrote:

On Tue, Dec 27, 2016 at 8:41 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/27 19:07, Amit Langote wrote:

Attached should fix that.

Here are the last two patches with additional information like other
patches. Forgot to do that yesterday.

0001 has the disadvantage that get_partition_for_tuple() acquires a
side effect. That seems undesirable. At the least, it needs to be
documented in the function's header comment.

That's true. How about we save away the original ecxt_scantuple at entry
and restore the same just before returning from the function? That way
there would be no side effect. 0001 implements that.

It's unclear to me why we need to do 0002. It doesn't seem like it
should be necessary, it doesn't seem like a good idea, and the commit
message you proposed is uninformative.

If a single BulkInsertState object is passed to
heap_insert()/heap_multi_insert() for different heaps corresponding to
different partitions (from one input tuple to next), tuples might end up
going into wrong heaps (like demonstrated in one of the reports [1]/messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com). A
simple solution is to disable bulk-insert in case of partitioned tables.

But my patch (or its motivations) was slightly wrongheaded, wherein I
conflated multi-insert stuff and bulk-insert considerations. I revised
0002 to not do that.

However if we disable bulk-insert mode, COPY's purported performance
benefit compared with INSERT is naught. Patch 0003 is a proposal to
implement bulk-insert mode even for partitioned tables. Basically,
allocate separate BulkInsertState objects for each partition and switch to
the appropriate one just before calling heap_insert()/heap_multi_insert().
Then to be able to use heap_multi_insert(), we must also manage buffered
tuples separately for each partition. Although, I didn't modify the limit
on number of buffered tuples and/or size of buffered tuples which controls
when we pause buffering and do heap_multi_insert() on buffered tuples.
Maybe, it should work slightly differently for the partitioned table case,
like for example, increase the overall limit on both the number of tuples
and tuple size in the partitioning case (I observed that increasing it 10x
or 100x helped to some degree). Thoughts on this?

Thanks,
Amit

[1]: /messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com
/messages/by-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ==cJpgEW4vA@mail.gmail.com

Attachments:

0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From 332c67c258a0f25f76c29ced23199fe0ee8e153e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 1/3] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels.  If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 29 ++++++++++++++++++++++-------
 src/backend/executor/execMain.c      |  2 --
 src/test/regress/expected/insert.out |  2 +-
 src/test/regress/sql/insert.sql      |  2 +-
 4 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f54e1bdf3f..0de1cf245a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1643,7 +1643,10 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	bool		isnull[PARTITION_MAX_KEYS];
 	int			cur_offset,
 				cur_index;
-	int			i;
+	int			i,
+				result;
+	ExprContext *ecxt = GetPerTupleExprContext(estate);
+	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
 	/* start with the root partitioned table */
 	parent = pd[0];
@@ -1672,7 +1675,14 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			slot = myslot;
 		}
 
-		/* Extract partition key from tuple */
+		/*
+		 * Extract partition key from tuple; FormPartitionKeyDatum() expects
+		 * ecxt_scantuple to point to the correct tuple slot (which might be
+		 * different from the slot we received from the caller if the
+		 * partitioned table of the current level has different tuple
+		 * descriptor from its parent).
+		 */
+		ecxt->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
@@ -1727,16 +1737,21 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
+			result = -1;
 			*failed_at = RelationGetRelid(parent->reldesc);
-			return -1;
+			break;
 		}
-		else if (parent->indexes[cur_index] < 0)
-			parent = pd[-parent->indexes[cur_index]];
-		else
+		else if (parent->indexes[cur_index] >= 0)
+		{
+			result = parent->indexes[cur_index];
 			break;
+		}
+		else
+			parent = pd[-parent->indexes[cur_index]];
 	}
 
-	return parent->indexes[cur_index];
+	ecxt->ecxt_scantuple = ecxt_scantuple_old;
+	return result;
 }
 
 /*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ff277d300a..6a9bc8372f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3167,9 +3167,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ca3134c34c..1c7b8047ee 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -302,7 +302,7 @@ drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 09c9879da1..c25dc14575 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -173,7 +173,7 @@ drop table list_parted cascade;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0002-No-multi-insert-and-bulk-insert-when-COPYing-into-pa.patchtext/x-diff; name=0002-No-multi-insert-and-bulk-insert-when-COPYing-into-pa.patchDownload
From 12b50bbda59be122b411c3847a0a04a03b17d013 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:28:37 +0900
Subject: [PATCH 2/3] No multi-insert and bulk-insert when COPYing into
 partitioned tables
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We might consider alleviating this restriction by allocating a
BulkInsertState object and managing tuple-buffering per partition.

Reported by: 高增琦, Venkata B Nagothi
Patch by: Amit Langote (with pointers from 高增琦)
Reports: https://www.postgresql.org/message-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ%3D%3DcJpgEW4vA%40mail.gmail.com
         https://www.postgresql.org/message-id/CAEyp7J9WiX0L3DoiNcRrY-9iyw%3DqP%2Bj%3DDLsAnNFF1xT2J1ggfQ%40mail.gmail.com
---
 src/backend/commands/copy.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f56b2ac49b..322f4260fd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2296,7 +2296,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2455,8 +2455,8 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
-		cstate->partition_dispatch_info != NULL ||
-		cstate->volatile_defexprs)
+		cstate->volatile_defexprs ||
+		cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		useHeapMultiInsert = false;
 	}
@@ -2480,7 +2480,13 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	/*
+	 * FIXME: We don't engage the bulk-insert mode for partitioned tables,
+	 * because the the heap relation is most likely change from one row to
+	 * next due to tuple-routing.
+	 */
+	if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		bistate = GetBulkInsertState();
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2701,7 +2707,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate != NULL)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
-- 
2.11.0

0003-Support-bulk-insert-mode-for-partitioned-tables.patchtext/x-diff; name=0003-Support-bulk-insert-mode-for-partitioned-tables.patchDownload
From 2a5c2bb661a956e118ccf3a3d93c4dc18943c1fd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 10:28:04 +0900
Subject: [PATCH 3/3] Support bulk-insert mode for partitioned tables

Currently, the heap layer (hio.c) supports a bulk-insert mode, which
is currently used by certain callers in copy.c, createas.c, etc.
Callers must pass a BulkInsertState object down to heapam routines
like heap_insert() or heap_multi_insert() along with the input row(s)
to engage this mode.

A single BulkInsertState object is good only for a given heap relation.
In case of a partitioned table, successive input rows may be mapped to
different partitions, so different heap relations.  We must use a separate
BulkInsertState object for each partition and switch to the same every
time a given partition is selected.

Also, if we are able to use multi-insert mode in CopyFrom() and hence
will buffer tuples, we must maintain separate buffer spaces and buffered
tuples counts for every partition.  Although, maximum limits on the
number of buffered tuples and buffered tuple size (across partitions)
are still the old compile-time constants, not scaled based on, say,
number of partitions.  It might be possible to raise that limit so that
enough tuples are buffered per partition in the worst case that input
tuples are randomly ordered.
---
 src/backend/commands/copy.c | 167 +++++++++++++++++++++++++++++---------------
 1 file changed, 109 insertions(+), 58 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 322f4260fd..c4397b4f78 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2296,15 +2296,20 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
-	bool		useHeapMultiInsert;
-	int			nBufferedTuples = 0;
 
-#define MAX_BUFFERED_TUPLES 1000
-	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
-	Size		bufferedTuplesSize = 0;
-	int			firstBufferedLineNo = 0;
+#define MAX_BUFFERED_TUPLES			1000
+#define MAX_BUFFERED_TUPLES_SIZE	65535
+	int		num_heaps;
+	bool   *useHeapMultiInsert = NULL;
+	BulkInsertState *bistate = NULL;
+	HeapTuple **bufferedTuples = NULL;	/* initialize to silence warning */
+	Size	   *bufferedTuplesSize = NULL;
+	int		   *firstBufferedLineNo = NULL;
+	int		   *nBufferedTuples = NULL;
+	Size		bufferedTuplesSize_total = 0;
+	int			nBufferedTuples_total = 0;
+	int			i;
 
 	Assert(cstate->rel);
 
@@ -2449,21 +2454,44 @@ CopyFrom(CopyState cstate)
 	 * BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default
 	 * expressions. Such triggers or expressions might query the table we're
 	 * inserting to, and act differently if the tuples that have already been
-	 * processed and prepared for insertion are not there.  We also can't do
-	 * it if the table is partitioned.
+	 * processed and prepared for insertion are not there.
+	 *
+	 * In case of a regular table there is only one heap, whereas in case of
+	 * a partitioned table, there are as many heaps as there are partitions.
+	 * We must manage buffered tuples separately for each heap.
 	 */
-	if ((resultRelInfo->ri_TrigDesc != NULL &&
-		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
-		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
-		cstate->volatile_defexprs ||
-		cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		useHeapMultiInsert = false;
-	}
-	else
+	num_heaps = cstate->num_partitions > 0 ? cstate->num_partitions : 1;
+
+	bufferedTuples = (HeapTuple **) palloc0(num_heaps * sizeof(HeapTuple *));
+	useHeapMultiInsert = (bool *) palloc(num_heaps * sizeof(bool));
+	nBufferedTuples = (int *) palloc0(num_heaps * sizeof(int));
+	bufferedTuplesSize = (Size *) palloc0(num_heaps * sizeof(Size));
+	firstBufferedLineNo = (int *) palloc0(num_heaps * sizeof(int));
+
+	/* Also, maintain separate bulk-insert state for every heap */
+	bistate = (BulkInsertState *) palloc(num_heaps * sizeof(BulkInsertState));
+
+	for (i = 0; i < num_heaps; i++)
 	{
-		useHeapMultiInsert = true;
-		bufferedTuples = palloc(MAX_BUFFERED_TUPLES * sizeof(HeapTuple));
+		/*
+		 * In case of a partitioned table, we check the individual partition's
+		 * TriggerDesc and not the root parent table's.
+		 */
+		ResultRelInfo *cur_rel = cstate->partitions ? cstate->partitions + i
+													: resultRelInfo;
+
+		if ((cur_rel->ri_TrigDesc != NULL &&
+			(cur_rel->ri_TrigDesc->trig_insert_before_row ||
+			 cur_rel->ri_TrigDesc->trig_insert_instead_row)) ||
+			cstate->volatile_defexprs)
+			useHeapMultiInsert[i] = false;
+		else
+			useHeapMultiInsert[i] = true;
+
+		if (useHeapMultiInsert[i])
+			bufferedTuples[i] = palloc(MAX_BUFFERED_TUPLES *
+									   sizeof(HeapTuple));
+		bistate[i] = GetBulkInsertState();
 	}
 
 	/* Prepare to catch AFTER triggers. */
@@ -2480,13 +2508,6 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	/*
-	 * FIXME: We don't engage the bulk-insert mode for partitioned tables,
-	 * because the the heap relation is most likely change from one row to
-	 * next due to tuple-routing.
-	 */
-	if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		bistate = GetBulkInsertState();
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2501,15 +2522,16 @@ CopyFrom(CopyState cstate)
 					   *oldslot;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
+		int			cur_heap = 0;
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (nBufferedTuples == 0)
+		if (nBufferedTuples_total == 0)
 		{
 			/*
 			 * Reset the per-tuple exprcontext. We can only do this if the
-			 * tuple buffer is empty. (Calling the context the per-tuple
-			 * memory context is a bit of a misnomer now.)
+			 * there are no buffered tuples. (Calling the context the
+			 * per-tuple memory context is a bit of a misnomer now.)
 			 */
 			ResetPerTupleExprContext(estate);
 		}
@@ -2560,6 +2582,7 @@ CopyFrom(CopyState cstate)
 												estate);
 			Assert(leaf_part_index >= 0 &&
 				   leaf_part_index < cstate->num_partitions);
+			cur_heap = leaf_part_index;
 
 			/*
 			 * Save the old ResultRelInfo and switch to the one corresponding
@@ -2588,7 +2611,13 @@ CopyFrom(CopyState cstate)
 			{
 				Relation	partrel = resultRelInfo->ri_RelationDesc;
 
+				/*
+				 * Allocate memory for the converted tuple in the per-tuple
+				 * context just like the original tuple.
+				 */
+				MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 				tuple = do_convert_tuple(tuple, map);
+				MemoryContextSwitchTo(oldcontext);
 
 				/*
 				 * We must use the partition's tuple descriptor from this
@@ -2598,7 +2627,7 @@ CopyFrom(CopyState cstate)
 				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
-				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 			}
 
 			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
@@ -2633,29 +2662,47 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
-				if (useHeapMultiInsert)
+				if (useHeapMultiInsert[cur_heap])
 				{
-					/* Add this tuple to the tuple buffer */
-					if (nBufferedTuples == 0)
-						firstBufferedLineNo = cstate->cur_lineno;
-					bufferedTuples[nBufferedTuples++] = tuple;
-					bufferedTuplesSize += tuple->t_len;
+					/* Add this tuple to the corresponding tuple buffer */
+					if (nBufferedTuples[cur_heap] == 0)
+						firstBufferedLineNo[cur_heap] = cstate->cur_lineno;
+					bufferedTuples[cur_heap][nBufferedTuples[cur_heap]++] =
+																	tuple;
+					bufferedTuplesSize[cur_heap] += tuple->t_len;
+
+					/* Count the current tuple toward the totals */
+					nBufferedTuples_total += nBufferedTuples[cur_heap];
+					bufferedTuplesSize_total += bufferedTuplesSize[cur_heap];
 
 					/*
-					 * If the buffer filled up, flush it.  Also flush if the
-					 * total size of all the tuples in the buffer becomes
-					 * large, to avoid using large amounts of memory for the
-					 * buffer when the tuples are exceptionally wide.
+					 * If enough tuples are buffered, flush them from the
+					 * individual buffers. Also flush if the total size of
+					 * all the buffered tuples becomes large, to avoid using
+					 * large amounts of buffer memory when the tuples are
+					 * exceptionally wide.
 					 */
-					if (nBufferedTuples == MAX_BUFFERED_TUPLES ||
-						bufferedTuplesSize > 65535)
+					if (nBufferedTuples_total == MAX_BUFFERED_TUPLES ||
+						bufferedTuplesSize_total > MAX_BUFFERED_TUPLES_SIZE)
 					{
-						CopyFromInsertBatch(cstate, estate, mycid, hi_options,
-											resultRelInfo, myslot, bistate,
-											nBufferedTuples, bufferedTuples,
-											firstBufferedLineNo);
-						nBufferedTuples = 0;
-						bufferedTuplesSize = 0;
+						for (i = 0; i < num_heaps; i++)
+						{
+							ResultRelInfo *cur_rel = cstate->partitions
+												   ? cstate->partitions + i
+												   : resultRelInfo;
+
+							CopyFromInsertBatch(cstate, estate, mycid,
+												hi_options, cur_rel,
+												myslot, bistate[i],
+												nBufferedTuples[i],
+												bufferedTuples[i],
+												firstBufferedLineNo[i]);
+							nBufferedTuples[i] = 0;
+							bufferedTuplesSize[i] = 0;
+						}
+
+						nBufferedTuples_total = 0;
+						bufferedTuplesSize_total = 0;
 					}
 				}
 				else
@@ -2664,7 +2711,7 @@ CopyFrom(CopyState cstate)
 
 					/* OK, store the tuple and create index entries for it */
 					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
+								hi_options, bistate[cur_heap]);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2698,18 +2745,22 @@ CopyFrom(CopyState cstate)
 	}
 
 	/* Flush any remaining buffered tuples */
-	if (nBufferedTuples > 0)
+	for (i = 0; i < num_heaps; i++)
+	{
+		ResultRelInfo *cur_rel = cstate->partitions ? cstate->partitions + i
+													: resultRelInfo;
+
 		CopyFromInsertBatch(cstate, estate, mycid, hi_options,
-							resultRelInfo, myslot, bistate,
-							nBufferedTuples, bufferedTuples,
-							firstBufferedLineNo);
+							cur_rel, myslot, bistate[i],
+							nBufferedTuples[i], bufferedTuples[i],
+							firstBufferedLineNo[i]);
+
+		FreeBulkInsertState(bistate[i]);
+	}
 
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	if (bistate != NULL)
-		FreeBulkInsertState(bistate);
-
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2802,7 +2853,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
+	heap_multi_insert(resultRelInfo->ri_RelationDesc,
 					  bufferedTuples,
 					  nBufferedTuples,
 					  mycid,
-- 
2.11.0

#256amul sul
sulamul@gmail.com
In reply to: Amit Langote (#255)
Re: Declarative partitioning - another take

Hi,

I got server crash due to assert failure at ATTACHing overlap rang
partition, here is test case to reproduce this:

CREATE TABLE test_parent(a int) PARTITION BY RANGE (a);
CREATE TABLE test_parent_part2 PARTITION OF test_parent FOR VALUES
FROM(100) TO(200);
CREATE TABLE test_parent_part1(a int NOT NULL);
ALTER TABLE test_parent ATTACH PARTITION test_parent_part1 FOR VALUES
FROM(1) TO(200);

I think, this bug exists in the following code of check_new_partition_bound():

767 if (equal || off1 != off2)
768 {
769 overlap = true;
770 with = boundinfo->indexes[off2 + 1];
771 }

When equal is true array index should not be 'off2 + 1'.

While reading code related to this, I wondered why
partition_bound_bsearch is not immediately returns when cmpval==0?

Apologise if this has been already reported.

Regards,
Amul

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

#257Keith Fiske
keith@omniti.com
In reply to: amul sul (#256)
1 attachment(s)
Re: Declarative partitioning - another take

Is there any reason for the exclusion of parent tables from the pg_tables
system catalog view? They do not show up in information_schema.tables as
well. I believe I found where to make the changes and I tested to make sure
it works for my simple case. Attached is my first attempt at patching
anything in core. Not sure if there's anywhere else this would need to be
fixed.

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

Attachments:

catalog_update_partitions.patchtext/x-patch; charset=US-ASCII; name=catalog_update_partitions.patchDownload
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 4df390a..c31d0d8 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1901,6 +1901,7 @@ CREATE VIEW tables AS
                   WHEN c.relkind = 'r' THEN 'BASE TABLE'
                   WHEN c.relkind = 'v' THEN 'VIEW'
                   WHEN c.relkind = 'f' THEN 'FOREIGN TABLE'
+                  WHEN c.relkind = 'P' THEN 'PARTITIONED TABLE'
                   ELSE null END
              AS character_data) AS table_type,
 
@@ -1912,7 +1913,7 @@ CREATE VIEW tables AS
            CAST(t.typname AS sql_identifier) AS user_defined_type_name,
 
            CAST(CASE WHEN c.relkind = 'r' OR
-                          (c.relkind IN ('v', 'f') AND
+                          (c.relkind IN ('v', 'f', 'P') AND
                            -- 1 << CMD_INSERT
                            pg_relation_is_updatable(c.oid, false) & 8 = 8)
                 THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into,
@@ -1923,7 +1924,7 @@ CREATE VIEW tables AS
     FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
            LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
 
-    WHERE c.relkind IN ('r', 'v', 'f')
+    WHERE c.relkind IN ('r', 'v', 'f', 'P')
           AND (NOT pg_is_other_temp_schema(nc.oid))
           AND (pg_has_role(c.relowner, 'USAGE')
                OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 31aade1..f4dc460 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -136,7 +136,7 @@ CREATE VIEW pg_tables AS
         C.relrowsecurity AS rowsecurity
     FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
          LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
-    WHERE C.relkind = 'r';
+    WHERE C.relkind = 'r' OR C.relkind = 'P';
 
 CREATE VIEW pg_matviews AS
     SELECT

#258Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: amul sul (#256)
1 attachment(s)
Re: Declarative partitioning - another take

Hi Amul,

On 2017/01/09 17:29, amul sul wrote:

I got server crash due to assert failure at ATTACHing overlap rang
partition, here is test case to reproduce this:

CREATE TABLE test_parent(a int) PARTITION BY RANGE (a);
CREATE TABLE test_parent_part2 PARTITION OF test_parent FOR VALUES
FROM(100) TO(200);
CREATE TABLE test_parent_part1(a int NOT NULL);
ALTER TABLE test_parent ATTACH PARTITION test_parent_part1 FOR VALUES
FROM(1) TO(200);

I think, this bug exists in the following code of check_new_partition_bound():

767 if (equal || off1 != off2)
768 {
769 overlap = true;
770 with = boundinfo->indexes[off2 + 1];
771 }

When equal is true array index should not be 'off2 + 1'.

Good catch. Attached patch should fix that. I observed crash with the
following command as well:

ALTER TABLE test_parent ATTACH PARTITION test_parent_part1 FOR VALUES FROM
(1) TO (300);

That's because there is one more case when the array index shouldn't be
off2 + 1 - the case where the bound at off2 is an upper bound (I'd wrongly
assumed that it's always a lower bound). Anyway, I rewrote the
surrounding comments to clarify the logic a bit.

While reading code related to this, I wondered why
partition_bound_bsearch is not immediately returns when cmpval==0?

partition_bound_bsearch() is meant to return the *greatest* index of the
bound less than or equal to the input bound ("probe"). But it seems to me
now that we would always return the first index at which we get 0 for
cmpval, albeit after wasting cycles to try to find even greater index.
Because we don't have duplicates in the datums array, once we encounter a
bound that is equal to probe, we are only going to find bounds that are
*greater than* probe if we continue looking right, only to turn back again
to return the equal index (which is wasted cycles in invoking the
partition key comparison function(s)). So, it perhaps makes sense to do
this per your suggestion:

@@ -1988,8 +2018,11 @@ partition_bound_bsearch(PartitionKey key,
PartitionBoundInfo boundinfo,
if (cmpval <= 0)
{
lo = mid;
*is_equal = (cmpval == 0);
+
+ if (*is_equal)
+ break;
}

Thanks,
Amit

Attachments:

0001-Fix-some-wrong-thinking-in-check_new_partition_bound.patchtext/x-diff; name=0001-Fix-some-wrong-thinking-in-check_new_partition_bound.patchDownload
From 7fe537f8e8efeac51c3b0cc91ac51a1aa39399cd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Jan 2017 17:43:36 +0900
Subject: [PATCH] Fix some wrong thinking in check_new_partition_bound()

Because a given range bound in the PartitionBoundInfo.datums array
is sometimes a lower range bound and at other times an upper range
bound, we must be careful when assuming which, especially when
interpreting the result of partition_bound_bsearch which returns
the index of the greatest bound that is less than or equal to probe.
Due to an error in thinking about the same, the relevant code in
check_new_partition_bound() caused invalid partition (index == -1)
to be chosen as the partition being overlapped.

Also, we need not continue searching for even greater bound in
partition_bound_bsearch() once we find the first bound that is *equal*
to the probe, because we don't have duplicate datums.  That spends
cycles needlessly.  Per suggestion from Amul Sul.

Reported by: Amul Sul
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAAJ_b94XgbqVoXMyxxs63CaqWoMS1o2gpHiU0F7yGnJBnvDc_A%40mail.gmail.com
---
 src/backend/catalog/partition.c            | 62 ++++++++++++++++++++++--------
 src/test/regress/expected/create_table.out | 10 ++++-
 src/test/regress/sql/create_table.sql      |  4 ++
 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f54e1bdf3f..df5652de4c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -741,35 +741,64 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
 
 					/*
-					 * Find the greatest index of a range bound that is less
-					 * than or equal with the new lower bound.
+					 * Firstly, find the greatest range bound that is less
+					 * than or equal to the new lower bound.
 					 */
 					off1 = partition_bound_bsearch(key, boundinfo, lower, true,
 												   &equal);
 
 					/*
-					 * If equal has been set to true, that means the new lower
-					 * bound is found to be equal with the bound at off1,
-					 * which clearly means an overlap with the partition at
-					 * index off1+1).
-					 *
-					 * Otherwise, check if there is a "gap" that could be
-					 * occupied by the new partition.  In case of a gap, the
-					 * new upper bound should not cross past the upper
-					 * boundary of the gap, that is, off2 == off1 should be
-					 * true.
+					 * off1 == -1 means that all existing bounds are greater
+					 * than the new lower bound.  In that case and the case
+					 * where no partition is defined between the bounds at
+					 * off1 and off1 + 1, we have a "gap" in the range that
+					 * could be occupied by the new partition.  We confirm if
+					 * so by checking whether the new upper bound is confined
+					 * within the gap.
 					 */
 					if (!equal && boundinfo->indexes[off1 + 1] < 0)
 					{
 						off2 = partition_bound_bsearch(key, boundinfo, upper,
 													   true, &equal);
 
+						/*
+						 * If the new upper bound is returned to be equal to
+						 * the bound at off2, the latter must be the upper
+						 * bound of some partition with which the new partition
+						 * clearly overlaps.
+						 *
+						 * Also, if bound at off2 is not same as the one
+						 * returned for the new lower bound (IOW,
+						 * off1 != off2), then the new partition overlaps at
+						 * least one partition.
+						 */
 						if (equal || off1 != off2)
 						{
 							overlap = true;
-							with = boundinfo->indexes[off2 + 1];
+							/*
+							 * The bound at off2 could be the lower bound of
+							 * the partition with which the new partition
+							 * overlaps.  In that case, use the upper bound
+							 * (that is, the bound at off2 + 1) to get the
+							 * index of that partition.
+							 */
+							if (boundinfo->indexes[off2] < 0)
+								with = boundinfo->indexes[off2 + 1];
+							else
+								with = boundinfo->indexes[off2];
 						}
 					}
+					/*
+					 * If equal has been set to true or if there is no "gap"
+					 * between the bound at off1 and that at off1 + 1, the new
+					 * partition will overlap some partition.  In the former
+					 * case, the new lower bound is found to be equal to the
+					 * bound at off1, which could only ever be true if the
+					 * latter is the lower bound of some partition.  It's
+					 * clear in such a case that the new partition overlaps
+					 * that partition, whose index we get using its upper
+					 * bound (that is, using the bound at off1 + 1).
+					 */
 					else
 					{
 						overlap = true;
@@ -1956,8 +1985,8 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 }
 
 /*
- * Binary search on a collection of partition bounds. Returns greatest index
- * of bound in array boundinfo->datums which is less or equal with *probe.
+ * Binary search on a collection of partition bounds. Returns greatest
+ * bound in array boundinfo->datums which is less than or equal to *probe
  * If all bounds in the array are greater than *probe, -1 is returned.
  *
  * *probe could either be a partition bound or a Datum array representing
@@ -1989,6 +2018,9 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 		{
 			lo = mid;
 			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
 		}
 		else
 			hi = mid - 1;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 783602314c..f01de7c04c 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -549,6 +549,12 @@ ERROR:  partition "fail_part" would overlap partition "part0"
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
 ERROR:  partition "fail_part" would overlap partition "part1"
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+ERROR:  partition "fail_part" would overlap partition "part3"
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -651,13 +657,15 @@ table part_c depends on table parted
 table part_c_1_10 depends on table part_c
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-NOTICE:  drop cascades to 14 other objects
+NOTICE:  drop cascades to 16 other objects
 DETAIL:  drop cascades to table part00
 drop cascades to table part10
 drop cascades to table part11
 drop cascades to table part12
 drop cascades to table part0
 drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
 drop cascades to table part_null_z
 drop cascades to table part_ab
 drop cascades to table part_1
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f1a67fddfa..c773709eaf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -517,6 +517,10 @@ CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1)
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
 
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
-- 
2.11.0

#259Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Keith Fiske (#257)
1 attachment(s)
Re: Declarative partitioning - another take

Hi Kieth,

On 2017/01/10 14:44, Keith Fiske wrote:

Is there any reason for the exclusion of parent tables from the pg_tables
system catalog view? They do not show up in information_schema.tables as
well. I believe I found where to make the changes and I tested to make sure
it works for my simple case. Attached is my first attempt at patching
anything in core. Not sure if there's anywhere else this would need to be
fixed.

That's an oversight. The original partitioning patch didn't touch
information_schema.sql and system_views.sql at all. I added the relkind =
'P' check in some other views as well, including what your patch considered.

Thanks,
Amit

Attachments:

0001-information_schema-system_views.sql-and-relkind-P.patchtext/x-diff; name=0001-information_schema-system_views.sql-and-relkind-P.patchDownload
From 9eef3e87b9a025d233aa4b935b50bb0c7633efbb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Jan 2017 18:12:25 +0900
Subject: [PATCH] information_schema/system_views.sql and relkind 'P'

Currently, partitioned table are not taken into account in various
information_schema and system views.

Reported by: Keith Fiske
Patch by: Kieth Fiske, Amit Langote
Reports: https://www.postgresql.org/message-id/CAG1_KcDJiZB=L6yOUO_bVufj2q2851_xdkfhw0JdcD_2VtKssw@mail.gmail.com
---
 src/backend/catalog/information_schema.sql | 37 +++++++++++++++---------------
 src/backend/catalog/system_views.sql       |  3 ++-
 2 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 4df390a763..318f195b81 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -453,7 +453,7 @@ CREATE VIEW check_constraints AS
       AND a.attnum > 0
       AND NOT a.attisdropped
       AND a.attnotnull
-      AND r.relkind = 'r'
+      AND r.relkind IN ('r', 'P')
       AND pg_has_role(r.relowner, 'USAGE');
 
 GRANT SELECT ON check_constraints TO PUBLIC;
@@ -525,7 +525,7 @@ CREATE VIEW column_domain_usage AS
           AND a.attrelid = c.oid
           AND a.atttypid = t.oid
           AND t.typtype = 'd'
-          AND c.relkind IN ('r', 'v', 'f')
+          AND c.relkind IN ('r', 'v', 'f', 'P')
           AND a.attnum > 0
           AND NOT a.attisdropped
           AND pg_has_role(t.typowner, 'USAGE');
@@ -564,7 +564,7 @@ CREATE VIEW column_privileges AS
                   pr_c.relowner
            FROM (SELECT oid, relname, relnamespace, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).*
                  FROM pg_class
-                 WHERE relkind IN ('r', 'v', 'f')
+                 WHERE relkind IN ('r', 'v', 'f', 'P')
                 ) pr_c (oid, relname, relnamespace, relowner, grantor, grantee, prtype, grantable),
                 pg_attribute a
            WHERE a.attrelid = pr_c.oid
@@ -586,7 +586,7 @@ CREATE VIEW column_privileges AS
                 ) pr_a (attrelid, attname, grantor, grantee, prtype, grantable),
                 pg_class c
            WHERE pr_a.attrelid = c.oid
-                 AND relkind IN ('r', 'v', 'f')
+                 AND relkind IN ('r', 'v', 'f', 'P')
          ) x,
          pg_namespace nc,
          pg_authid u_grantor,
@@ -629,7 +629,7 @@ CREATE VIEW column_udt_usage AS
     WHERE a.attrelid = c.oid
           AND a.atttypid = t.oid
           AND nc.oid = c.relnamespace
-          AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
+          AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f', 'P')
           AND pg_has_role(coalesce(bt.typowner, t.typowner), 'USAGE');
 
 GRANT SELECT ON column_udt_usage TO PUBLIC;
@@ -738,7 +738,7 @@ CREATE VIEW columns AS
            CAST('NEVER' AS character_data) AS is_generated,
            CAST(null AS character_data) AS generation_expression,
 
-           CAST(CASE WHEN c.relkind = 'r' OR
+           CAST(CASE WHEN c.relkind IN ('r', 'P') OR
                           (c.relkind IN ('v', 'f') AND
                            pg_column_is_updatable(c.oid, a.attnum, false))
                 THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_updatable
@@ -753,7 +753,7 @@ CREATE VIEW columns AS
 
     WHERE (NOT pg_is_other_temp_schema(nc.oid))
 
-          AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
+          AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f', 'P')
 
           AND (pg_has_role(c.relowner, 'USAGE')
                OR has_column_privilege(c.oid, a.attnum,
@@ -789,7 +789,7 @@ CREATE VIEW constraint_column_usage AS
             AND d.objid = c.oid
             AND c.connamespace = nc.oid
             AND c.contype = 'c'
-            AND r.relkind = 'r'
+            AND r.relkind IN ('r', 'P')
             AND NOT a.attisdropped
 
         UNION ALL
@@ -841,7 +841,7 @@ CREATE VIEW constraint_table_usage AS
     WHERE c.connamespace = nc.oid AND r.relnamespace = nr.oid
           AND ( (c.contype = 'f' AND c.confrelid = r.oid)
              OR (c.contype IN ('p', 'u') AND c.conrelid = r.oid) )
-          AND r.relkind = 'r'
+          AND r.relkind IN ('r', 'P')
           AND pg_has_role(r.relowner, 'USAGE');
 
 GRANT SELECT ON constraint_table_usage TO PUBLIC;
@@ -1774,7 +1774,7 @@ CREATE VIEW table_constraints AS
     WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace
           AND c.conrelid = r.oid
           AND c.contype NOT IN ('t', 'x')  -- ignore nonstandard constraints
-          AND r.relkind = 'r'
+          AND r.relkind IN ('r', 'P')
           AND (NOT pg_is_other_temp_schema(nr.oid))
           AND (pg_has_role(r.relowner, 'USAGE')
                -- SELECT privilege omitted, per SQL standard
@@ -1804,7 +1804,7 @@ CREATE VIEW table_constraints AS
           AND a.attnotnull
           AND a.attnum > 0
           AND NOT a.attisdropped
-          AND r.relkind = 'r'
+          AND r.relkind IN ('r', 'P')
           AND (NOT pg_is_other_temp_schema(nr.oid))
           AND (pg_has_role(r.relowner, 'USAGE')
                -- SELECT privilege omitted, per SQL standard
@@ -1854,7 +1854,7 @@ CREATE VIEW table_privileges AS
          ) AS grantee (oid, rolname)
 
     WHERE c.relnamespace = nc.oid
-          AND c.relkind IN ('r', 'v')
+          AND c.relkind IN ('r', 'v', 'P')
           AND c.grantee = grantee.oid
           AND c.grantor = u_grantor.oid
           AND c.prtype IN ('INSERT', 'SELECT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER')
@@ -1901,6 +1901,7 @@ CREATE VIEW tables AS
                   WHEN c.relkind = 'r' THEN 'BASE TABLE'
                   WHEN c.relkind = 'v' THEN 'VIEW'
                   WHEN c.relkind = 'f' THEN 'FOREIGN TABLE'
+                  WHEN c.relkind = 'P' THEN 'PARTITIONED TABLE'
                   ELSE null END
              AS character_data) AS table_type,
 
@@ -1911,7 +1912,7 @@ CREATE VIEW tables AS
            CAST(nt.nspname AS sql_identifier) AS user_defined_type_schema,
            CAST(t.typname AS sql_identifier) AS user_defined_type_name,
 
-           CAST(CASE WHEN c.relkind = 'r' OR
+           CAST(CASE WHEN c.relkind IN ('r', 'P') OR
                           (c.relkind IN ('v', 'f') AND
                            -- 1 << CMD_INSERT
                            pg_relation_is_updatable(c.oid, false) & 8 = 8)
@@ -1923,7 +1924,7 @@ CREATE VIEW tables AS
     FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
            LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
 
-    WHERE c.relkind IN ('r', 'v', 'f')
+    WHERE c.relkind IN ('r', 'v', 'f', 'P')
           AND (NOT pg_is_other_temp_schema(nc.oid))
           AND (pg_has_role(c.relowner, 'USAGE')
                OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
@@ -2442,7 +2443,7 @@ CREATE VIEW view_column_usage AS
           AND dt.refclassid = 'pg_catalog.pg_class'::regclass
           AND dt.refobjid = t.oid
           AND t.relnamespace = nt.oid
-          AND t.relkind IN ('r', 'v', 'f')
+          AND t.relkind IN ('r', 'v', 'f', 'P')
           AND t.oid = a.attrelid
           AND dt.refobjsubid = a.attnum
           AND pg_has_role(t.relowner, 'USAGE');
@@ -2476,7 +2477,7 @@ CREATE VIEW view_routine_usage AS
          pg_depend dp, pg_proc p, pg_namespace np
 
     WHERE nv.oid = v.relnamespace
-          AND v.relkind = 'v'
+          AND v.relkind IN ('v')
           AND v.oid = dv.refobjid
           AND dv.refclassid = 'pg_catalog.pg_class'::regclass
           AND dv.classid = 'pg_catalog.pg_rewrite'::regclass
@@ -2520,7 +2521,7 @@ CREATE VIEW view_table_usage AS
           AND dt.refclassid = 'pg_catalog.pg_class'::regclass
           AND dt.refobjid = t.oid
           AND t.relnamespace = nt.oid
-          AND t.relkind IN ('r', 'v', 'f')
+          AND t.relkind IN ('r', 'v', 'f', 'P')
           AND pg_has_role(t.relowner, 'USAGE');
 
 GRANT SELECT ON view_table_usage TO PUBLIC;
@@ -2673,7 +2674,7 @@ CREATE VIEW element_types AS
                   a.attnum, a.atttypid, a.attcollation
            FROM pg_class c, pg_attribute a
            WHERE c.oid = a.attrelid
-                 AND c.relkind IN ('r', 'v', 'f', 'c')
+                 AND c.relkind IN ('r', 'v', 'f', 'c', 'P')
                  AND attnum > 0 AND NOT attisdropped
 
            UNION ALL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 31aade102b..de2a301566 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -136,7 +136,7 @@ CREATE VIEW pg_tables AS
         C.relrowsecurity AS rowsecurity
     FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
          LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
-    WHERE C.relkind = 'r';
+    WHERE C.relkind IN ('r', 'P');
 
 CREATE VIEW pg_matviews AS
     SELECT
@@ -285,6 +285,7 @@ SELECT
 		 WHEN rel.relkind = 'm' THEN 'materialized view'::text
 		 WHEN rel.relkind = 'S' THEN 'sequence'::text
 		 WHEN rel.relkind = 'f' THEN 'foreign table'::text END AS objtype,
+		 WHEN rel.relkind = 'P' THEN 'partitioned table'::text END AS objtype,
 	rel.relnamespace AS objnamespace,
 	CASE WHEN pg_table_is_visible(rel.oid)
 	     THEN quote_ident(rel.relname)
-- 
2.11.0

#260Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#251)
7 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/05 5:50, Robert Haas wrote:

On Tue, Dec 27, 2016 at 3:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Patches 0001 to 0006 unchanged.

Committed 0001 earlier, as mentioned in a separate email. Committed
0002 and part of 0003.

Thanks! I realized however that the approach I used in 0002 of passing
the original slot to ExecConstraints() fails in certain situations. For
example, if a BR trigger changes the tuple, the original slot would not
receive those changes, so it will be wrong to use such a tuple anymore.
In attached 0001, I switched back to the approach of converting the
partition-tupdesc-based tuple back to the root partitioned table's format.
The converted tuple contains the changes by BR triggers, if any. Sorry
about some unnecessary work.

But I'm skeptical that the as-patched-by-0003
logic in generate_partition_qual() makes sense. You do this:

result = list_concat(generate_partition_qual(parent),
copyObject(rel->rd_partcheck));

/* Mark Vars with correct attnos */
result = map_partition_varattnos(result, rel, parent);

But that has the effect of applying map_partition_varattnos to
everything in rel->rd_partcheck in addition to applying it to
everything returned by generate_partition_qual() on the parent, which
doesn't seem right.

I've replaced this portion of the code with (as also mentioned below):

/* Quick copy */
if (rel->rd_partcheck != NIL)
return copyObject(rel->rd_partcheck);

Down below (for the case when the partition qual is not cached, we now do
this:

my_qual = get_qual_from_partbound(rel, parent, bound);

/* Add the parent's quals to the list (if any) */
if (parent->rd_rel->relispartition)
result = list_concat(generate_partition_qual(parent), my_qual);
else
result = my_qual;

/*
* Change Vars to have partition's attnos instead of the parent's.
* We do this after we concatenate the parent's quals, because
* we want every Var in it to bear this relation's attnos.
*/
result = map_partition_varattnos(result, rel, parent);

Which is then cached wholly in rd_partcheck.

As for your concern whether it's correct to do so, consider that doing
generate_partition_qual() on the parent returns qual with Vars that bear
the parent's attnos (which is OK as far parent as partition is concerned).
To apply the qual to the current relation as partition, we must change
the Vars to have this relation's attnos.

Also, don't we want to do map_partition_varattnos() just ONCE, rather
than on every call to this function? I think maybe your concern is
that the parent might be changed without a relcache flush on the
child, but I don't quite see how that could happen. If the parent's
tuple descriptor changes, surely the child's tuple descriptor would
have to be altered at the same time...

Makes sense. I fixed so that we return copyObject(rel->rd_partcheck), if
it's non-NIL, instead of generating parent's qual and doing the mapping
again. For some reason, I thought we couldn't save the mapped version in
the relcache.

By the way, in addition to the previously mentioned bug of RETURNING, I
found that WITH CHECK OPTION didn't work correctly as well. In fact
automatically updatable views failed to consider partitioned tables at
all. Patch 0007 is addressed towards fixing that.

Thanks,
Amit

Attachments:

0001-Fix-reporting-of-violation-in-ExecConstraints-again.patchtext/x-diff; name=0001-Fix-reporting-of-violation-in-ExecConstraints-again.patchDownload
From e408234633c01817d6a2313fdbdccdb4f0057c1e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 15:53:10 +0900
Subject: [PATCH 1/7] Fix reporting of violation in ExecConstraints, again

We decided in f1b4c771ea74f42447dccaed42ffcdcccf3aa694 that passing
the original slot (one containing the tuple formatted per root
partitioned table's tupdesc) to ExecConstraints(), but that breaks
certain cases.  Imagine what would happen if a BR trigger changed the
tuple - the original slot would not contain those changes.
So, it seems better to convert (if necessary) the tuple formatted
per partition tupdesc after tuple-routing back to the root table's
format and use the converted tuple to make val_desc shown in the
message if an error occurs.
---
 src/backend/commands/copy.c            |  6 ++--
 src/backend/executor/execMain.c        | 53 +++++++++++++++++++++++++++++-----
 src/backend/executor/nodeModifyTable.c |  5 ++--
 src/include/executor/executor.h        |  3 +-
 src/test/regress/expected/insert.out   | 18 ++++++++++--
 src/test/regress/sql/insert.sql        | 17 ++++++++++-
 6 files changed, 82 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f56b2ac49b..65eb167087 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2491,8 +2491,7 @@ CopyFrom(CopyState cstate)
 
 	for (;;)
 	{
-		TupleTableSlot *slot,
-					   *oldslot;
+		TupleTableSlot *slot;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
 
@@ -2534,7 +2533,6 @@ CopyFrom(CopyState cstate)
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
 		/* Determine the partition to heap_insert the tuple into */
-		oldslot = slot;
 		if (cstate->partition_dispatch_info)
 		{
 			int			leaf_part_index;
@@ -2625,7 +2623,7 @@ CopyFrom(CopyState cstate)
 				/* Check the constraints of the tuple */
 				if (cstate->rel->rd_att->constr ||
 					resultRelInfo->ri_PartitionCheck)
-					ExecConstraints(resultRelInfo, slot, oldslot, estate);
+					ExecConstraints(resultRelInfo, slot, estate);
 
 				if (useHeapMultiInsert)
 				{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ff277d300a..332c54c819 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1763,8 +1763,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
  */
 void
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, TupleTableSlot *orig_slot,
-				EState *estate)
+				TupleTableSlot *slot, EState *estate)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -1787,23 +1786,37 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			{
 				char	   *val_desc;
 				Relation	orig_rel = rel;
-				TupleDesc	orig_tupdesc = tupdesc;
+				TupleDesc	orig_tupdesc = RelationGetDescr(rel);
 
 				/*
-				 * choose the correct relation to build val_desc from the
-				 * tuple contained in orig_slot
+				 * In case where the tuple is routed, it's been converted
+				 * to the partition's rowtype, which might differ from the
+				 * root table's.  We must convert it back to the root table's
+				 * type so that val_desc shown error message matches the
+				 * input tuple.
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
+					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TupleConversionMap	*map;
+
 					rel = resultRelInfo->ri_PartitionRoot;
 					tupdesc = RelationGetDescr(rel);
+					/* a reverse map */
+					map = convert_tuples_by_name(orig_tupdesc, tupdesc,
+								gettext_noop("could not convert row type"));
+					if (map != NULL)
+					{
+						tuple = do_convert_tuple(tuple, map);
+						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+					}
 				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 				modifiedCols = bms_union(insertedCols, updatedCols);
 				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-														 orig_slot,
+														 slot,
 														 tupdesc,
 														 modifiedCols,
 														 64);
@@ -1830,15 +1843,27 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			/* See the comment above. */
 			if (resultRelInfo->ri_PartitionRoot)
 			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TupleDesc	old_tupdesc = RelationGetDescr(rel);
+				TupleConversionMap	*map;
+
 				rel = resultRelInfo->ri_PartitionRoot;
 				tupdesc = RelationGetDescr(rel);
+				/* a reverse map */
+				map = convert_tuples_by_name(old_tupdesc, tupdesc,
+							gettext_noop("could not convert row type"));
+				if (map != NULL)
+				{
+					tuple = do_convert_tuple(tuple, map);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
 			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 			modifiedCols = bms_union(insertedCols, updatedCols);
 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-													 orig_slot,
+													 slot,
 													 tupdesc,
 													 modifiedCols,
 													 64);
@@ -1860,15 +1885,27 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		/* See the comment above. */
 		if (resultRelInfo->ri_PartitionRoot)
 		{
+			HeapTuple	tuple = ExecFetchSlotTuple(slot);
+			TupleDesc	old_tupdesc = RelationGetDescr(rel);
+			TupleConversionMap	*map;
+
 			rel = resultRelInfo->ri_PartitionRoot;
 			tupdesc = RelationGetDescr(rel);
+			/* a reverse map */
+			map = convert_tuples_by_name(old_tupdesc, tupdesc,
+						gettext_noop("could not convert row type"));
+			if (map != NULL)
+			{
+				tuple = do_convert_tuple(tuple, map);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			}
 		}
 
 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 		modifiedCols = bms_union(insertedCols, updatedCols);
 		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-												 orig_slot,
+												 slot,
 												 tupdesc,
 												 modifiedCols,
 												 64);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4692427e60..23e04893b8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,6 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = slot;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -433,7 +432,7 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Check the constraints of the tuple
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, oldslot, estate);
+			ExecConstraints(resultRelInfo, slot, estate);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
@@ -994,7 +993,7 @@ lreplace:;
 		 * tuple-routing is performed here, hence the slot remains unchanged.
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, slot, estate);
+			ExecConstraints(resultRelInfo, slot, estate);
 
 		/*
 		 * replace the heap tuple
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index b9c7f72903..3e8d64686e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -195,8 +195,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, TupleTableSlot *orig_slot,
-				EState *estate);
+				TupleTableSlot *slot, EState *estate);
 extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 					 TupleTableSlot *slot, EState *estate);
 extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ca3134c34c..501d50eeac 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -335,10 +335,24 @@ select tableoid::regclass, * from p;
 
 truncate p;
 alter table p add constraint check_b check (b = 3);
+create function p11_trig_fn()
+returns trigger AS
+$$
+begin
+  NEW.b := 4;
+  return NEW;
+end;
+$$
+language plpgsql;
+create trigger p11_trig before insert ON p11
+  for each row execute procedure p11_trig_fn();
 -- check that correct input row is shown when constraint check_b fails on p11
--- after "(1, 2)" is routed to it
+-- after "(1, 2)" is routed to it (actually "(1, 4)" would be shown due to the
+-- BR trigger p11_trig_fn)
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
-DETAIL:  Failing row contains (1, 2).
+DETAIL:  Failing row contains (1, 4).
+drop trigger p11_trig on p11;
+drop function p11_trig_fn();
 -- cleanup
 drop table p, p1, p11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 09c9879da1..22aa94e181 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -197,9 +197,24 @@ select tableoid::regclass, * from p;
 
 truncate p;
 alter table p add constraint check_b check (b = 3);
+create function p11_trig_fn()
+returns trigger AS
+$$
+begin
+  NEW.b := 4;
+  return NEW;
+end;
+$$
+language plpgsql;
+create trigger p11_trig before insert ON p11
+  for each row execute procedure p11_trig_fn();
+
 -- check that correct input row is shown when constraint check_b fails on p11
--- after "(1, 2)" is routed to it
+-- after "(1, 2)" is routed to it (actually "(1, 4)" would be shown due to the
+-- BR trigger p11_trig_fn)
 insert into p values (1, 2);
+drop trigger p11_trig on p11;
+drop function p11_trig_fn();
 
 -- cleanup
 drop table p, p1, p11;
-- 
2.11.0

0002-Fix-a-bug-in-how-we-generate-partition-constraints.patchtext/x-diff; name=0002-Fix-a-bug-in-how-we-generate-partition-constraints.patchDownload
From d8f1dd0f87f2c99c951cae15e0b90fbc96a9783b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 15 Dec 2016 16:27:04 +0900
Subject: [PATCH 2/7] Fix a bug in how we generate partition constraints

Move the code for doing parent attnos to child attnos mapping for Vars
in partition constraint expressions to a separate function
map_partition_varattnos() and call it from the appropriate places.
Doing it in get_qual_from_partbound(), as is now, would produce wrong
result in certain multi-level partitioning cases, because it only
considers the current pair of parent-child relations.  In certain
multi-level partitioning cases, attnums for the same key attribute(s)
might differ between various levels causing the same attribute to be
numbered differently in different instances of the Var corresponding
to a given attribute.

With this commit, in generate_partition_qual(), we first generate the
the whole partition constraint (considering all levels of partitioning)
and then do the mapping, so that Vars in the final expression are
numbered according the leaf relation (to which it is supposed to apply).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c           | 99 +++++++++++++++----------------
 src/backend/commands/tablecmds.c          |  7 ++-
 src/include/catalog/partition.h           |  1 +
 src/test/regress/expected/alter_table.out | 30 ++++++++++
 src/test/regress/sql/alter_table.sql      | 25 ++++++++
 5 files changed, 110 insertions(+), 52 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f54e1bdf3f..874e69d8d6 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -852,10 +852,6 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
-	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
-	AttrNumber	parent_attno;
-	AttrNumber *partition_attnos;
-	bool		found_whole_row;
 
 	Assert(key != NULL);
 
@@ -876,38 +872,51 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 				 (int) key->strategy);
 	}
 
-	/*
-	 * Translate vars in the generated expression to have correct attnos. Note
-	 * that the vars in my_qual bear attnos dictated by key which carries
-	 * physical attnos of the parent.  We must allow for a case where physical
-	 * attnos of a partition can be different from the parent.
-	 */
-	partition_attnos = (AttrNumber *)
-		palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
-	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
-		 parent_attno++)
+	return my_qual;
+}
+
+/*
+ * map_partition_varattnos - maps varattno of any Vars in expr from the
+ * parent attno to partition attno.
+ *
+ * We must allow for a case where physical attnos of a partition can be
+ * different from the parent's.
+ */
+List *
+map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+{
+	TupleDesc	tupdesc = RelationGetDescr(parent);
+	AttrNumber	attno;
+	AttrNumber *part_attnos;
+	bool		found_whole_row;
+
+	if (expr == NIL)
+		return NIL;
+
+	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
+	for (attno = 1; attno <= tupdesc->natts; attno++)
 	{
-		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
 		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	partition_attno;
+		AttrNumber	part_attno;
 
 		if (attribute->attisdropped)
 			continue;
 
-		partition_attno = get_attnum(RelationGetRelid(rel), attname);
-		partition_attnos[parent_attno - 1] = partition_attno;
+		part_attno = get_attnum(RelationGetRelid(partrel), attname);
+		part_attnos[attno - 1] = part_attno;
 	}
 
-	my_qual = (List *) map_variable_attnos((Node *) my_qual,
-										   1, 0,
-										   partition_attnos,
-										   parent_tupdesc->natts,
-										   &found_whole_row);
-	/* there can never be a whole-row reference here */
+	expr = (List *) map_variable_attnos((Node *) expr,
+										1, 0,
+										part_attnos,
+										tupdesc->natts,
+										&found_whole_row);
+	/* There can never be a whole-row reference here */
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	return my_qual;
+	return expr;
 }
 
 /*
@@ -1496,27 +1505,15 @@ generate_partition_qual(Relation rel)
 	/* Guard against stack overflow due to overly deep partition tree */
 	check_stack_depth();
 
+	/* Quick copy */
+	if (rel->rd_partcheck != NIL)
+		return copyObject(rel->rd_partcheck);
+
 	/* Grab at least an AccessShareLock on the parent table */
 	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
 					   AccessShareLock);
 
-	/* Quick copy */
-	if (rel->rd_partcheck)
-	{
-		if (parent->rd_rel->relispartition)
-			result = list_concat(generate_partition_qual(parent),
-								 copyObject(rel->rd_partcheck));
-		else
-			result = copyObject(rel->rd_partcheck);
-
-		heap_close(parent, AccessShareLock);
-		return result;
-	}
-
 	/* Get pg_class.relpartbound */
-	if (!rel->rd_rel->relispartition)	/* should not happen */
-		elog(ERROR, "relation \"%s\" has relispartition = false",
-			 RelationGetRelationName(rel));
 	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u",
@@ -1533,20 +1530,22 @@ generate_partition_qual(Relation rel)
 
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
-	/* If requested, add parent's quals to the list (if any) */
+	/* Add the parent's quals to the list (if any) */
 	if (parent->rd_rel->relispartition)
-	{
-		List	   *parent_check;
-
-		parent_check = generate_partition_qual(parent);
-		result = list_concat(parent_check, my_qual);
-	}
+		result = list_concat(generate_partition_qual(parent), my_qual);
 	else
 		result = my_qual;
 
-	/* Save a copy of my_qual in the relcache */
+	/*
+	 * Change Vars to have partition's attnos instead of the parent's.
+	 * We do this after we concatenate the parent's quals, because
+	 * we want every Var in it to bear this relation's attnos.
+	 */
+	result = map_partition_varattnos(result, rel, parent);
+
+	/* Save a copy in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-	rel->rd_partcheck = copyObject(my_qual);
+	rel->rd_partcheck = copyObject(result);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* Keep the parent locked until commit */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 42558ec42e..f913e87bc8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13429,6 +13429,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			Oid			part_relid = lfirst_oid(lc);
 			Relation	part_rel;
 			Expr	   *constr;
+			List	   *my_constr;
 
 			/* Lock already taken */
 			if (part_relid != RelationGetRelid(attachRel))
@@ -13451,8 +13452,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			tab = ATGetQueueEntry(wqueue, part_rel);
 
 			constr = linitial(partConstraint);
-			tab->partition_constraint = make_ands_implicit((Expr *) constr);
-
+			my_constr = make_ands_implicit((Expr *) constr);
+			tab->partition_constraint = map_partition_varattnos(my_constr,
+																part_rel,
+																rel);
 			/* keep our lock until commit */
 			if (part_rel != attachRel)
 				heap_close(part_rel, NoLock);
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1c3c4d6ac3..537f0aad67 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,6 +77,7 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d8028c6747..b290bc810e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3339,3 +3339,33 @@ drop cascades to table part_2
 drop cascades to table part_5
 drop cascades to table part_5_a
 drop cascades to table part_1
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+ attrelid | attname | attnum 
+----------+---------+--------
+ p        | a       |      1
+ p1       | a       |      2
+ p11      | a       |      4
+(3 rows)
+
+alter table p1 attach partition p11 for values from (2) to (5);
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+ERROR:  partition constraint is violated by some row
+-- cleanup
+drop table p, p1 cascade;
+NOTICE:  drop cascades to table p11
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 2265266517..5d07e2ede4 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2191,3 +2191,28 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+
+-- more tests for certain multi-level partitioning scenarios
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+-- attnum for key attribute 'a' is different in p, p1, and p11
+select attrelid::regclass, attname, attnum
+from pg_attribute
+where attname = 'a'
+ and (attrelid = 'p'::regclass
+   or attrelid = 'p1'::regclass
+   or attrelid = 'p11'::regclass);
+
+alter table p1 attach partition p11 for values from (2) to (5);
+
+insert into p1 (a, b) values (2, 3);
+-- check that partition validation scan correctly detects violating rows
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+-- cleanup
+drop table p, p1 cascade;
-- 
2.11.0

0003-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0003-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 9c636bea31c2faf0715a12a2dd4cdea73e4e7f64 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 3/7] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 42 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 65eb167087..baab1d0eeb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2425,7 +2425,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f913e87bc8..37940f4469 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,7 +1324,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 332c54c819..7a073c6df3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,13 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1327,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3169,7 +3194,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3e8d64686e..502807e53f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 501d50eeac..b3cfd44ffd 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -354,5 +354,11 @@ ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 4).
 drop trigger p11_trig on p11;
 drop function p11_trig_fn();
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p, p1, p11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 22aa94e181..c5a75a505f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -216,5 +216,10 @@ insert into p values (1, 2);
 drop trigger p11_trig on p11;
 drop function p11_trig_fn();
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p, p1, p11;
-- 
2.11.0

0004-Add-some-more-tests-for-tuple-routing.patchtext/x-diff; name=0004-Add-some-more-tests-for-tuple-routing.patchDownload
From 3821a771a264a9bbd101f434f2bf76c883ece91b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 21 Dec 2016 10:51:32 +0900
Subject: [PATCH 4/7] Add some more tests for tuple-routing

We fixed some issues with how PartitionDispatch related code handled
multi-level partitioned tables in commit a25665088d, but didn't add
any tests.

Reported by: Dmitry Ivanov, Robert Haas
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/0d5b64c9-fa05-4dab-93e7-56576d1193ca%40postgrespro.ru
         https://www.postgresql.org/message-id/CA%2BTgmoZ86v1G%2Bzx9etMiSQaBBvYMKfU-iitqZArSh5z0n8Q4cA%40mail.gmail.com
---
 src/test/regress/expected/insert.out | 38 +++++++++++++++++++++++++++++++++++-
 src/test/regress/sql/insert.sql      | 18 +++++++++++++++++
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b3cfd44ffd..f4b97bf228 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -285,6 +285,34 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2 | EE | 10
 (8 rows)
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+   tableoid    | a  | min_b | max_b 
+---------------+----+-------+-------
+ part_aa_bb    | aa |       |      
+ part_cc_dd    | cc |       |      
+ part_ee_ff1   | Ff |     1 |     9
+ part_ee_ff2   | Ff |    10 |    19
+ part_ee_ff3_1 | Ff |    20 |    24
+ part_ee_ff3_2 | Ff |    25 |    29
+ part_gg2_1    | gg |     1 |     4
+ part_gg2_2    | gg |     5 |     9
+ part_null     |    |     1 |     1
+(9 rows)
+
 -- cleanup
 drop table range_parted cascade;
 NOTICE:  drop cascades to 4 other objects
@@ -293,13 +321,21 @@ drop cascades to table part2
 drop cascades to table part3
 drop cascades to table part4
 drop table list_parted cascade;
-NOTICE:  drop cascades to 6 other objects
+NOTICE:  drop cascades to 14 other objects
 DETAIL:  drop cascades to table part_aa_bb
 drop cascades to table part_cc_dd
 drop cascades to table part_null
 drop cascades to table part_ee_ff
 drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
+drop cascades to table part_ee_ff3
+drop cascades to table part_ee_ff3_1
+drop cascades to table part_ee_ff3_2
+drop cascades to table part_gg
+drop cascades to table part_gg1
+drop cascades to table part_gg2
+drop cascades to table part_gg2_1
+drop cascades to table part_gg2_2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index c5a75a505f..1916c9849a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -167,6 +167,24 @@ insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
 
+-- some more tests to exercise tuple-routing with multi-level partitioning
+create table part_gg partition of list_parted for values in ('gg') partition by range (b);
+create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
+create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
+create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
+
+create table part_ee_ff3 partition of part_ee_ff for values from (20) to (30) partition by range (b);
+create table part_ee_ff3_1 partition of part_ee_ff3 for values from (20) to (25);
+create table part_ee_ff3_2 partition of part_ee_ff3 for values from (25) to (30);
+
+truncate list_parted;
+insert into list_parted values ('aa'), ('cc');
+insert into list_parted select 'Ff', s.a from generate_series(1, 29) s(a);
+insert into list_parted select 'gg', s.a from generate_series(1, 9) s(a);
+insert into list_parted (b) values (1);
+select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_parted group by 1, 2 order by 1;
+
 -- cleanup
 drop table range_parted cascade;
 drop table list_parted cascade;
-- 
2.11.0

0005-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0005-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From 82a8593dfdb49bf84833dddd7c725a5545e16d04 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 5/7] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 8 ++++++--
 src/backend/catalog/partition.c        | 5 ++---
 src/backend/commands/analyze.c         | 1 +
 src/backend/executor/execMain.c        | 7 ++++---
 src/backend/executor/execQual.c        | 2 +-
 src/include/access/tupconvert.h        | 1 +
 6 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b17ceafa6e..bbbd271e1f 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -202,6 +202,7 @@ convert_tuples_by_position(TupleDesc indesc,
 TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg)
 {
 	TupleConversionMap *map;
@@ -216,11 +217,14 @@ convert_tuples_by_name(TupleDesc indesc,
 	/*
 	 * Check to see if the map is one-to-one and the tuple types are the same.
 	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * to inject the right OID into the tuple datum.  In the partitioning
+	 * case (!consider_typeid), tdhasoids must always match between indesc
+	 * and outdesc, so we need not require tdtypeid's to be the same.)
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		(!consider_typeid || indesc->tdtypeid == outdesc->tdtypeid))
 	{
+		Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);
 		same = true;
 		for (i = 0; i < n; i++)
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 874e69d8d6..61ad945062 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1054,7 +1054,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			 */
 			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
 			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
-												   tupdesc,
+												   tupdesc, false,
 								gettext_noop("could not convert row type"));
 		}
 		else
@@ -1660,12 +1660,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e3e1a53072..31bec9f961 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1419,6 +1419,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 					map = convert_tuples_by_name(RelationGetDescr(childrel),
 												 RelationGetDescr(onerel),
+												 true,
 								 gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7a073c6df3..d3ebf7e146 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1828,7 +1828,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					rel = resultRelInfo->ri_PartitionRoot;
 					tupdesc = RelationGetDescr(rel);
 					/* a reverse map */
-					map = convert_tuples_by_name(orig_tupdesc, tupdesc,
+					map = convert_tuples_by_name(orig_tupdesc, tupdesc, false,
 								gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
@@ -1875,7 +1875,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				rel = resultRelInfo->ri_PartitionRoot;
 				tupdesc = RelationGetDescr(rel);
 				/* a reverse map */
-				map = convert_tuples_by_name(old_tupdesc, tupdesc,
+				map = convert_tuples_by_name(old_tupdesc, tupdesc, false,
 							gettext_noop("could not convert row type"));
 				if (map != NULL)
 				{
@@ -1917,7 +1917,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			rel = resultRelInfo->ri_PartitionRoot;
 			tupdesc = RelationGetDescr(rel);
 			/* a reverse map */
-			map = convert_tuples_by_name(old_tupdesc, tupdesc,
+			map = convert_tuples_by_name(old_tupdesc, tupdesc, false,
 						gettext_noop("could not convert row type"));
 			if (map != NULL)
 			{
@@ -3189,6 +3189,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		 * partition from the parent's type to the partition's.
 		 */
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+													 false,
 								 gettext_noop("could not convert row type"));
 
 		InitResultRelInfo(leaf_part_rri,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index bf007b7efd..346b2ff17e 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2928,7 +2928,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 
 		/* prepare map from old to new attribute numbers */
 		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
+											 cstate->outdesc, true,
 								 gettext_noop("could not convert row type"));
 		cstate->initialized = true;
 
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index e86cfd56c8..231fe872d7 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -36,6 +36,7 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg);
 
 extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc,
-- 
2.11.0

0006-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchtext/x-diff; name=0006-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchDownload
From 53f4532635d4a4faaec90c42d8bfa54fbbffc9c5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 27 Dec 2016 16:56:58 +0900
Subject: [PATCH 6/7] Fix RETURNING to work correctly after tuple-routing

In ExecInsert(), do not switch back to the original resultRelInfo until
after we finish ExecProcessReturning(), so that RETURNING projection
is done considering the partition the tuple was routed to.  For the
projection to work correctly, we must initialize the same for each
leaf partition during ModifyTableState initialization.

With this commit, map_partition_varattnos() now accepts one more argument
viz. target_varno.  Previously, it assumed varno = 1 for its input
expressions, which limited its applicability.  It was enought so far
since its usage was limited to partition constraints. To use it with
expressions such as an INSERT's returning list, we must be prepared for
varnos != 1 as in the change above.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c        |  8 ++++---
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        |  4 ++--
 src/backend/executor/nodeModifyTable.c | 44 +++++++++++++++++++++++++++-------
 src/include/catalog/partition.h        |  3 ++-
 src/test/regress/expected/insert.out   | 24 ++++++++++++++++++-
 src/test/regress/sql/insert.sql        | 16 ++++++++++++-
 7 files changed, 84 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 61ad945062..d08e057a2e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -883,7 +883,8 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * different from the parent's.
  */
 List *
-map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent)
 {
 	TupleDesc	tupdesc = RelationGetDescr(parent);
 	AttrNumber	attno;
@@ -908,7 +909,7 @@ map_partition_varattnos(List *expr, Relation partrel, Relation parent)
 	}
 
 	expr = (List *) map_variable_attnos((Node *) expr,
-										1, 0,
+										target_varno, 0,
 										part_attnos,
 										tupdesc->natts,
 										&found_whole_row);
@@ -1540,8 +1541,9 @@ generate_partition_qual(Relation rel)
 	 * Change Vars to have partition's attnos instead of the parent's.
 	 * We do this after we concatenate the parent's quals, because
 	 * we want every Var in it to bear this relation's attnos.
+	 * It's safe to assume varno = 1 here.
 	 */
-	result = map_partition_varattnos(result, rel, parent);
+	result = map_partition_varattnos(result, 1, rel, parent);
 
 	/* Save a copy in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 37940f4469..97042321c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13453,6 +13453,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			constr = linitial(partConstraint);
 			my_constr = make_ands_implicit((Expr *) constr);
 			tab->partition_constraint = map_partition_varattnos(my_constr,
+																1,
 																part_rel,
 																rel);
 			/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d3ebf7e146..060b036ddf 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1279,10 +1279,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		/*
 		 * This is not our own partition constraint, but rather an ancestor's.
 		 * So any Vars in it bear the ancestor's attribute numbers.  We must
-		 * switch them to our own.
+		 * switch them to our own. (dummy varno = 1)
 		 */
 		if (partition_check != NIL)
-			partition_check = map_partition_varattnos(partition_check,
+			partition_check = map_partition_varattnos(partition_check, 1,
 													  resultRelationDesc,
 													  partition_root);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 23e04893b8..f46070fde7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,6 +262,7 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	TupleTableSlot *result = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -573,12 +574,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
-	if (saved_resultRelInfo)
-	{
-		resultRelInfo = saved_resultRelInfo;
-		estate->es_result_relation_info = resultRelInfo;
-	}
-
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -596,9 +591,15 @@ ExecInsert(ModifyTableState *mtstate,
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
-		return ExecProcessReturning(resultRelInfo, slot, planSlot);
+		result = ExecProcessReturning(resultRelInfo, slot, planSlot);
 
-	return NULL;
+	if (saved_resultRelInfo)
+	{
+		resultRelInfo = saved_resultRelInfo;
+		estate->es_result_relation_info = resultRelInfo;
+	}
+
+	return result;
 }
 
 /* ----------------------------------------------------------------
@@ -1785,6 +1786,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		TupleTableSlot *slot;
 		ExprContext *econtext;
+		List		*returningList;
 
 		/*
 		 * Initialize result tuple slot and assign its rowtype using the first
@@ -1817,6 +1819,32 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
+
+		/*
+		 * Build a projection for each leaf partition rel.  Note that we
+		 * didn't build the returningList for each partition within the
+		 * planner, but simple translation of the varattnos for each
+		 * partition will suffice.  This only occurs for the INSERT case;
+		 * UPDATE/DELETE are handled above.
+		 */
+		resultRelInfo = mtstate->mt_partitions;
+		returningList = linitial(node->returningLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *rlist,
+					   *rliststate;
+
+			/* varno = node->nominalRelation */
+			rlist = map_partition_varattnos(returningList,
+											node->nominalRelation,
+											partrel, rel);
+			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+			resultRelInfo->ri_projectReturning =
+				ExecBuildProjectionInfo(rliststate, econtext, slot,
+									 resultRelInfo->ri_RelationDesc->rd_att);
+			resultRelInfo++;
+		}
 	}
 	else
 	{
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 537f0aad67..df7dcce331 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index f4b97bf228..2c7e4a8337 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -396,5 +396,27 @@ drop function p11_trig_fn();
 insert into p1 (a, b) values (2, 3);
 ERROR:  new row for relation "p11" violates partition constraint
 DETAIL:  Failing row contains (3, 2).
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+  a  | b | min | max 
+-----+---+-----+-----
+ p11 | 1 |   2 |   4
+ p12 | 1 |   5 |   9
+ p2  | 1 |  10 |  19
+ p3  | 1 |  20 |  29
+ p4  | 1 |  30 |  39
+(5 rows)
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 1916c9849a..aa0cacb744 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -239,5 +239,19 @@ drop function p11_trig_fn();
 -- selected by tuple-routing
 insert into p1 (a, b) values (2, 3);
 
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
-- 
2.11.0

0007-Fix-some-issues-with-views-and-partitioned-tables.patchtext/x-diff; name=0007-Fix-some-issues-with-views-and-partitioned-tables.patchDownload
From f6855b58b72128db9cfeb8d648eff4b0879e32fc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 15:33:02 +0900
Subject: [PATCH 7/7] Fix some issues with views and partitioned tables

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
them constraint expressions having been suitably converted for each
partition (think map_partition_varattnos on Vars in the expressions
just like with regular check constraints).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/executor/execMain.c               | 34 +++++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c        | 40 +++++++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c          |  3 +-
 src/test/regress/expected/updatable_views.out | 24 ++++++++++++++++
 src/test/regress/sql/updatable_views.sql      | 19 +++++++++++++
 5 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 060b036ddf..634a02bd8e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2000,6 +2000,40 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 			Bitmapset  *insertedCols;
 			Bitmapset  *updatedCols;
 
+			/*
+			 * In case where the tuple is routed, it's been converted
+			 * to the partition's rowtype, which might differ from the
+			 * root table's.  We must convert it back to the root table's
+			 * type so that it's shown correctly in the error message.
+			 */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TupleDesc	old_tupdesc = RelationGetDescr(rel);
+				TupleConversionMap	*map;
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				/* a reverse map */
+				map = convert_tuples_by_name(old_tupdesc, tupdesc, false,
+								gettext_noop("could not convert row type"));
+				if (map != NULL)
+				{
+					tuple = do_convert_tuple(tuple, map);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
+			}
+
+			/*
+			 * choose the correct relation to build val_desc from the
+			 * tuple contained in orig_slot
+			 */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+			}
+
 			switch (wco->kind)
 			{
 					/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f46070fde7..5ccf2321de 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1780,6 +1780,46 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * Build WITH CHECK OPTION constraints for each leaf partition rel.
+	 * Note that we didn't build the returningList for each partition within
+	 * the planner, but simple translation of the varattnos for each partition
+	 * will suffice.  This only occurs for the INSERT case; UPDATE/DELETE are
+	 * handled above.
+	 */
+	if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0)
+	{
+		List		*wcoList;
+
+		Assert(operation == CMD_INSERT);
+		resultRelInfo = mtstate->mt_partitions;
+		wcoList = linitial(node->withCheckOptionLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *mapped_wcoList;
+			List	   *wcoExprs = NIL;
+			ListCell   *ll;
+
+			/* varno = node->nominalRelation */
+			mapped_wcoList = map_partition_varattnos(wcoList,
+													 node->nominalRelation,
+													 partrel, rel);
+			foreach(ll, mapped_wcoList)
+			{
+				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+				ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+											   mtstate->mt_plans[i]);
+
+				wcoExprs = lappend(wcoExprs, wcoExpr);
+			}
+
+			resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
+			resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+			resultRelInfo++;
+		}
+	}
+
+	/*
 	 * Initialize RETURNING projections if needed.
 	 */
 	if (node->returningLists)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d1ff3b20b6..d3e44fb135 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2249,7 +2249,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	if (base_rte->rtekind != RTE_RELATION ||
 		(base_rte->relkind != RELKIND_RELATION &&
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
-		 base_rte->relkind != RELKIND_VIEW))
+		 base_rte->relkind != RELKIND_VIEW &&
+		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
 
 	if (base_rte->tablesample)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 6fba613f0f..b6b61422bf 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2465,3 +2465,27 @@ ERROR:  new row violates check option for view "v1"
 DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+ tableoid | a | b 
+----------+---+---
+ p11      | 1 | 2
+(1 row)
+
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+ERROR:  new row violates check option for view "pv_wco"
+DETAIL:  Failing row contains (1, 2).
+drop view pv, pv_wco;
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index bb9a3a6174..cd4f5a811d 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1112,3 +1112,22 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
 
 DROP VIEW v1;
 DROP TABLE t1;
+
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+drop view pv, pv_wco;
+drop table p, p1, p11;
-- 
2.11.0

#261Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#255)
3 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/06 20:23, Amit Langote wrote:

On 2017/01/05 3:26, Robert Haas wrote:

It's unclear to me why we need to do 0002. It doesn't seem like it
should be necessary, it doesn't seem like a good idea, and the commit
message you proposed is uninformative.

If a single BulkInsertState object is passed to
heap_insert()/heap_multi_insert() for different heaps corresponding to
different partitions (from one input tuple to next), tuples might end up
going into wrong heaps (like demonstrated in one of the reports [1]). A
simple solution is to disable bulk-insert in case of partitioned tables.

But my patch (or its motivations) was slightly wrongheaded, wherein I
conflated multi-insert stuff and bulk-insert considerations. I revised
0002 to not do that.

Ragnar Ouchterlony pointed out [1]/messages/by-id/732dfc84-25f5-413c-1eee-0bfa7a370093@agama.tv on pgsql-bugs that 0002 wasn't correct.
Attaching updated 0002 along with rebased 0001 and 0003.

Thanks,
Amit

[1]: /messages/by-id/732dfc84-25f5-413c-1eee-0bfa7a370093@agama.tv
/messages/by-id/732dfc84-25f5-413c-1eee-0bfa7a370093@agama.tv

Attachments:

0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From f2a64348021c7dba1f96d0c8b4e3e253f635b019 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 1/3] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels.  If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 29 ++++++++++++++++++++++-------
 src/backend/executor/execMain.c      |  2 --
 src/test/regress/expected/insert.out |  2 +-
 src/test/regress/sql/insert.sql      |  2 +-
 4 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f54e1bdf3f..0de1cf245a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1643,7 +1643,10 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	bool		isnull[PARTITION_MAX_KEYS];
 	int			cur_offset,
 				cur_index;
-	int			i;
+	int			i,
+				result;
+	ExprContext *ecxt = GetPerTupleExprContext(estate);
+	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
 	/* start with the root partitioned table */
 	parent = pd[0];
@@ -1672,7 +1675,14 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			slot = myslot;
 		}
 
-		/* Extract partition key from tuple */
+		/*
+		 * Extract partition key from tuple; FormPartitionKeyDatum() expects
+		 * ecxt_scantuple to point to the correct tuple slot (which might be
+		 * different from the slot we received from the caller if the
+		 * partitioned table of the current level has different tuple
+		 * descriptor from its parent).
+		 */
+		ecxt->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
@@ -1727,16 +1737,21 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
+			result = -1;
 			*failed_at = RelationGetRelid(parent->reldesc);
-			return -1;
+			break;
 		}
-		else if (parent->indexes[cur_index] < 0)
-			parent = pd[-parent->indexes[cur_index]];
-		else
+		else if (parent->indexes[cur_index] >= 0)
+		{
+			result = parent->indexes[cur_index];
 			break;
+		}
+		else
+			parent = pd[-parent->indexes[cur_index]];
 	}
 
-	return parent->indexes[cur_index];
+	ecxt->ecxt_scantuple = ecxt_scantuple_old;
+	return result;
 }
 
 /*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ff277d300a..6a9bc8372f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3167,9 +3167,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index ca3134c34c..1c7b8047ee 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -302,7 +302,7 @@ drop cascades to table part_ee_ff1
 drop cascades to table part_ee_ff2
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 09c9879da1..c25dc14575 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -173,7 +173,7 @@ drop table list_parted cascade;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0002-No-multi-insert-and-bulk-insert-when-COPYing-into-pa.patchtext/x-diff; name=0002-No-multi-insert-and-bulk-insert-when-COPYing-into-pa.patchDownload
From 6ac55787450862284556484acebb7346bedf751b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:28:37 +0900
Subject: [PATCH 2/3] No multi-insert and bulk-insert when COPYing into
 partitioned tables
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We might consider alleviating this restriction by allocating a
BulkInsertState object and managing tuple-buffering per partition.

Reported by: 高增琦, Venkata B Nagothi, Ragnar Ouchterlony
Patch by: Amit Langote (with pointers from 高增琦)
Reports: https://www.postgresql.org/message-id/CAFmBtr32FDOqofo8yG-4mjzL1HnYHxXK5S9OGFJ%3D%3DcJpgEW4vA%40mail.gmail.com
         https://www.postgresql.org/message-id/CAEyp7J9WiX0L3DoiNcRrY-9iyw%3DqP%2Bj%3DDLsAnNFF1xT2J1ggfQ%40mail.gmail.com
         https://www.postgresql.org/message-id/16d73804-c9cd-14c5-463e-5caad563ff77%40agama.tv
---
 src/backend/commands/copy.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f56b2ac49b..9624c93f6b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2296,7 +2296,7 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate;
+	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
 	bool		useHeapMultiInsert;
 	int			nBufferedTuples = 0;
@@ -2455,8 +2455,8 @@ CopyFrom(CopyState cstate)
 	if ((resultRelInfo->ri_TrigDesc != NULL &&
 		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
 		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
-		cstate->partition_dispatch_info != NULL ||
-		cstate->volatile_defexprs)
+		cstate->volatile_defexprs ||
+		cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		useHeapMultiInsert = false;
 	}
@@ -2480,7 +2480,14 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	bistate = GetBulkInsertState();
+	/*
+	 * FIXME: We don't engage the bulk-insert mode for partitioned tables,
+	 * because the the heap relation is most likely change from one row to
+	 * next due to tuple-routing.
+	 */
+	if (cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		bistate = GetBulkInsertState();
+
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2701,7 +2708,8 @@ CopyFrom(CopyState cstate)
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	FreeBulkInsertState(bistate);
+	if (bistate != NULL)
+		FreeBulkInsertState(bistate);
 
 	MemoryContextSwitchTo(oldcontext);
 
-- 
2.11.0

0003-Support-bulk-insert-mode-for-partitioned-tables.patchtext/x-diff; name=0003-Support-bulk-insert-mode-for-partitioned-tables.patchDownload
From d9ad05ea0197b0d79e281cf2c4a366d596085077 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 10:28:04 +0900
Subject: [PATCH 3/3] Support bulk-insert mode for partitioned tables

Currently, the heap layer (hio.c) supports a bulk-insert mode, which
is currently used by certain callers in copy.c, createas.c, etc.
Callers must pass a BulkInsertState object down to heapam routines
like heap_insert() or heap_multi_insert() along with the input row(s)
to engage this mode.

A single BulkInsertState object is good only for a given heap relation.
In case of a partitioned table, successive input rows may be mapped to
different partitions, so different heap relations.  We must use a separate
BulkInsertState object for each partition and switch to the same every
time a given partition is selected.

Also, if we are able to use multi-insert mode in CopyFrom() and hence
will buffer tuples, we must maintain separate buffer spaces and buffered
tuples counts for every partition.  Although, maximum limits on the
number of buffered tuples and buffered tuple size (across partitions)
are still the old compile-time constants, not scaled based on, say,
number of partitions.  It might be possible to raise that limit so that
enough tuples are buffered per partition in the worst case that input
tuples are randomly ordered.
---
 src/backend/commands/copy.c | 168 ++++++++++++++++++++++++++++----------------
 1 file changed, 109 insertions(+), 59 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9624c93f6b..c4397b4f78 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2296,15 +2296,20 @@ CopyFrom(CopyState cstate)
 	ErrorContextCallback errcallback;
 	CommandId	mycid = GetCurrentCommandId(true);
 	int			hi_options = 0; /* start with default heap_insert options */
-	BulkInsertState bistate = NULL;
 	uint64		processed = 0;
-	bool		useHeapMultiInsert;
-	int			nBufferedTuples = 0;
 
-#define MAX_BUFFERED_TUPLES 1000
-	HeapTuple  *bufferedTuples = NULL;	/* initialize to silence warning */
-	Size		bufferedTuplesSize = 0;
-	int			firstBufferedLineNo = 0;
+#define MAX_BUFFERED_TUPLES			1000
+#define MAX_BUFFERED_TUPLES_SIZE	65535
+	int		num_heaps;
+	bool   *useHeapMultiInsert = NULL;
+	BulkInsertState *bistate = NULL;
+	HeapTuple **bufferedTuples = NULL;	/* initialize to silence warning */
+	Size	   *bufferedTuplesSize = NULL;
+	int		   *firstBufferedLineNo = NULL;
+	int		   *nBufferedTuples = NULL;
+	Size		bufferedTuplesSize_total = 0;
+	int			nBufferedTuples_total = 0;
+	int			i;
 
 	Assert(cstate->rel);
 
@@ -2449,21 +2454,44 @@ CopyFrom(CopyState cstate)
 	 * BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default
 	 * expressions. Such triggers or expressions might query the table we're
 	 * inserting to, and act differently if the tuples that have already been
-	 * processed and prepared for insertion are not there.  We also can't do
-	 * it if the table is partitioned.
+	 * processed and prepared for insertion are not there.
+	 *
+	 * In case of a regular table there is only one heap, whereas in case of
+	 * a partitioned table, there are as many heaps as there are partitions.
+	 * We must manage buffered tuples separately for each heap.
 	 */
-	if ((resultRelInfo->ri_TrigDesc != NULL &&
-		 (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
-		  resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
-		cstate->volatile_defexprs ||
-		cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		useHeapMultiInsert = false;
-	}
-	else
+	num_heaps = cstate->num_partitions > 0 ? cstate->num_partitions : 1;
+
+	bufferedTuples = (HeapTuple **) palloc0(num_heaps * sizeof(HeapTuple *));
+	useHeapMultiInsert = (bool *) palloc(num_heaps * sizeof(bool));
+	nBufferedTuples = (int *) palloc0(num_heaps * sizeof(int));
+	bufferedTuplesSize = (Size *) palloc0(num_heaps * sizeof(Size));
+	firstBufferedLineNo = (int *) palloc0(num_heaps * sizeof(int));
+
+	/* Also, maintain separate bulk-insert state for every heap */
+	bistate = (BulkInsertState *) palloc(num_heaps * sizeof(BulkInsertState));
+
+	for (i = 0; i < num_heaps; i++)
 	{
-		useHeapMultiInsert = true;
-		bufferedTuples = palloc(MAX_BUFFERED_TUPLES * sizeof(HeapTuple));
+		/*
+		 * In case of a partitioned table, we check the individual partition's
+		 * TriggerDesc and not the root parent table's.
+		 */
+		ResultRelInfo *cur_rel = cstate->partitions ? cstate->partitions + i
+													: resultRelInfo;
+
+		if ((cur_rel->ri_TrigDesc != NULL &&
+			(cur_rel->ri_TrigDesc->trig_insert_before_row ||
+			 cur_rel->ri_TrigDesc->trig_insert_instead_row)) ||
+			cstate->volatile_defexprs)
+			useHeapMultiInsert[i] = false;
+		else
+			useHeapMultiInsert[i] = true;
+
+		if (useHeapMultiInsert[i])
+			bufferedTuples[i] = palloc(MAX_BUFFERED_TUPLES *
+									   sizeof(HeapTuple));
+		bistate[i] = GetBulkInsertState();
 	}
 
 	/* Prepare to catch AFTER triggers. */
@@ -2480,14 +2508,6 @@ CopyFrom(CopyState cstate)
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
 	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
-	/*
-	 * FIXME: We don't engage the bulk-insert mode for partitioned tables,
-	 * because the the heap relation is most likely change from one row to
-	 * next due to tuple-routing.
-	 */
-	if (cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
-		bistate = GetBulkInsertState();
-
 	econtext = GetPerTupleExprContext(estate);
 
 	/* Set up callback to identify error line number */
@@ -2502,15 +2522,16 @@ CopyFrom(CopyState cstate)
 					   *oldslot;
 		bool		skip_tuple;
 		Oid			loaded_oid = InvalidOid;
+		int			cur_heap = 0;
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (nBufferedTuples == 0)
+		if (nBufferedTuples_total == 0)
 		{
 			/*
 			 * Reset the per-tuple exprcontext. We can only do this if the
-			 * tuple buffer is empty. (Calling the context the per-tuple
-			 * memory context is a bit of a misnomer now.)
+			 * there are no buffered tuples. (Calling the context the
+			 * per-tuple memory context is a bit of a misnomer now.)
 			 */
 			ResetPerTupleExprContext(estate);
 		}
@@ -2561,6 +2582,7 @@ CopyFrom(CopyState cstate)
 												estate);
 			Assert(leaf_part_index >= 0 &&
 				   leaf_part_index < cstate->num_partitions);
+			cur_heap = leaf_part_index;
 
 			/*
 			 * Save the old ResultRelInfo and switch to the one corresponding
@@ -2589,7 +2611,13 @@ CopyFrom(CopyState cstate)
 			{
 				Relation	partrel = resultRelInfo->ri_RelationDesc;
 
+				/*
+				 * Allocate memory for the converted tuple in the per-tuple
+				 * context just like the original tuple.
+				 */
+				MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 				tuple = do_convert_tuple(tuple, map);
+				MemoryContextSwitchTo(oldcontext);
 
 				/*
 				 * We must use the partition's tuple descriptor from this
@@ -2599,7 +2627,7 @@ CopyFrom(CopyState cstate)
 				slot = cstate->partition_tuple_slot;
 				Assert(slot != NULL);
 				ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
-				ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 			}
 
 			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
@@ -2634,29 +2662,47 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_PartitionCheck)
 					ExecConstraints(resultRelInfo, slot, oldslot, estate);
 
-				if (useHeapMultiInsert)
+				if (useHeapMultiInsert[cur_heap])
 				{
-					/* Add this tuple to the tuple buffer */
-					if (nBufferedTuples == 0)
-						firstBufferedLineNo = cstate->cur_lineno;
-					bufferedTuples[nBufferedTuples++] = tuple;
-					bufferedTuplesSize += tuple->t_len;
+					/* Add this tuple to the corresponding tuple buffer */
+					if (nBufferedTuples[cur_heap] == 0)
+						firstBufferedLineNo[cur_heap] = cstate->cur_lineno;
+					bufferedTuples[cur_heap][nBufferedTuples[cur_heap]++] =
+																	tuple;
+					bufferedTuplesSize[cur_heap] += tuple->t_len;
+
+					/* Count the current tuple toward the totals */
+					nBufferedTuples_total += nBufferedTuples[cur_heap];
+					bufferedTuplesSize_total += bufferedTuplesSize[cur_heap];
 
 					/*
-					 * If the buffer filled up, flush it.  Also flush if the
-					 * total size of all the tuples in the buffer becomes
-					 * large, to avoid using large amounts of memory for the
-					 * buffer when the tuples are exceptionally wide.
+					 * If enough tuples are buffered, flush them from the
+					 * individual buffers. Also flush if the total size of
+					 * all the buffered tuples becomes large, to avoid using
+					 * large amounts of buffer memory when the tuples are
+					 * exceptionally wide.
 					 */
-					if (nBufferedTuples == MAX_BUFFERED_TUPLES ||
-						bufferedTuplesSize > 65535)
+					if (nBufferedTuples_total == MAX_BUFFERED_TUPLES ||
+						bufferedTuplesSize_total > MAX_BUFFERED_TUPLES_SIZE)
 					{
-						CopyFromInsertBatch(cstate, estate, mycid, hi_options,
-											resultRelInfo, myslot, bistate,
-											nBufferedTuples, bufferedTuples,
-											firstBufferedLineNo);
-						nBufferedTuples = 0;
-						bufferedTuplesSize = 0;
+						for (i = 0; i < num_heaps; i++)
+						{
+							ResultRelInfo *cur_rel = cstate->partitions
+												   ? cstate->partitions + i
+												   : resultRelInfo;
+
+							CopyFromInsertBatch(cstate, estate, mycid,
+												hi_options, cur_rel,
+												myslot, bistate[i],
+												nBufferedTuples[i],
+												bufferedTuples[i],
+												firstBufferedLineNo[i]);
+							nBufferedTuples[i] = 0;
+							bufferedTuplesSize[i] = 0;
+						}
+
+						nBufferedTuples_total = 0;
+						bufferedTuplesSize_total = 0;
 					}
 				}
 				else
@@ -2665,7 +2711,7 @@ CopyFrom(CopyState cstate)
 
 					/* OK, store the tuple and create index entries for it */
 					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
-								hi_options, bistate);
+								hi_options, bistate[cur_heap]);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2699,18 +2745,22 @@ CopyFrom(CopyState cstate)
 	}
 
 	/* Flush any remaining buffered tuples */
-	if (nBufferedTuples > 0)
+	for (i = 0; i < num_heaps; i++)
+	{
+		ResultRelInfo *cur_rel = cstate->partitions ? cstate->partitions + i
+													: resultRelInfo;
+
 		CopyFromInsertBatch(cstate, estate, mycid, hi_options,
-							resultRelInfo, myslot, bistate,
-							nBufferedTuples, bufferedTuples,
-							firstBufferedLineNo);
+							cur_rel, myslot, bistate[i],
+							nBufferedTuples[i], bufferedTuples[i],
+							firstBufferedLineNo[i]);
+
+		FreeBulkInsertState(bistate[i]);
+	}
 
 	/* Done, clean up */
 	error_context_stack = errcallback.previous;
 
-	if (bistate != NULL)
-		FreeBulkInsertState(bistate);
-
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
@@ -2803,7 +2853,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 	 * before calling it.
 	 */
 	oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-	heap_multi_insert(cstate->rel,
+	heap_multi_insert(resultRelInfo->ri_RelationDesc,
 					  bufferedTuples,
 					  nBufferedTuples,
 					  mycid,
-- 
2.11.0

#262Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#260)
Re: Declarative partitioning - another take

On Tue, Jan 10, 2017 at 6:06 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/01/05 5:50, Robert Haas wrote:

On Tue, Dec 27, 2016 at 3:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Patches 0001 to 0006 unchanged.

Committed 0001 earlier, as mentioned in a separate email. Committed
0002 and part of 0003.

Thanks! I realized however that the approach I used in 0002 of passing
the original slot to ExecConstraints() fails in certain situations. For
example, if a BR trigger changes the tuple, the original slot would not
receive those changes, so it will be wrong to use such a tuple anymore.
In attached 0001, I switched back to the approach of converting the
partition-tupdesc-based tuple back to the root partitioned table's format.
The converted tuple contains the changes by BR triggers, if any. Sorry
about some unnecessary work.

Hmm. Even with this patch, I wonder if this is really correct. I
mean, isn't the root of the problem here that ExecConstraints() is
expecting that resultRelInfo matches slot, and it doesn't? And why
isn't that also a problem for the things that get passed resultRelInfo
and slot after tuple routing and before ExecConstraints? In
particular, in copy.c, ExecBRInsertTriggers.

Committed 0002.

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

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

#263Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#262)
Re: Declarative partitioning - another take

On 2017/01/14 6:24, Robert Haas wrote:

On Tue, Jan 10, 2017 at 6:06 AM, Amit Langote wrote:

Thanks! I realized however that the approach I used in 0002 of passing
the original slot to ExecConstraints() fails in certain situations. For
example, if a BR trigger changes the tuple, the original slot would not
receive those changes, so it will be wrong to use such a tuple anymore.
In attached 0001, I switched back to the approach of converting the
partition-tupdesc-based tuple back to the root partitioned table's format.
The converted tuple contains the changes by BR triggers, if any. Sorry
about some unnecessary work.

Hmm. Even with this patch, I wonder if this is really correct. I
mean, isn't the root of the problem here that ExecConstraints() is
expecting that resultRelInfo matches slot, and it doesn't?

The problem is that whereas the SlotValueDescription that we build to show
in the error message should be based on the tuple that was passed to
ExecInsert() or whatever NextCopyFrom() returned for CopyFrom() to
process, it might fail to be the case if the tuple was needed to be
converted after tuple routing. slot (the tuple in it and its tuple
descriptor) and resultRelInfo that ExecConstraint() receives *do*
correspond with each other, even after possible tuple conversion following
tuple-routing, and hence constraint checking itself works fine (since
commit 2ac3ef7a01 [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ac3ef7). As said, it's the val_desc built to show in the
error message being based on the converted-for-partition tuple that could
be seen as a problem - is it acceptable if we showed in the error message
whatever the converted-for-partition tuple looks like which might have
columns ordered differently from the root table? If so, we could simply
forget the whole thing, including reverting f1b4c771 [2]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=f1b4c77.

An example:

create table p (a int, b char, c int) partition by list (a);
create table p1 (b char, c int, a int); -- note the column order
alter table p attach partition p1 for values in (1);
alter table p add constraint check_b check (b = 'x');

insert into p values (1, 'y', 1);
ERROR: new row for relation "p1" violates check constraint "check_b"
DETAIL: Failing row contains (y, 1, 1).

Note that "(y, 1, 1)" results from using p1's descriptor on the converted
tuple. As long that's clear and acceptable, I think we need not worry
about this patch and revert the previously committed patch for this "problem".

And why
isn't that also a problem for the things that get passed resultRelInfo
and slot after tuple routing and before ExecConstraints? In
particular, in copy.c, ExecBRInsertTriggers.

If I explained the problem (as I'm seeing it) well enough above, you may
see why that's not an issue in other cases. Other sub-routines viz.
ExecBRInsertTriggers, ExecWithCheckOptions for RLS INSERT WITH CHECK
policies, ExecARInsertTriggers, etc. do receive the correct slot and
resultRelInfo for whatever they do with a given tuple and the relation
(partition) it is being inserted into. However, if we do consider the
above to be a problem, then we would need to fix ExecWithCheckOptions() to
do whatever we decide ExecConstraints() should do, because it can also
report WITH CHECK violation for a tuple.

Committed 0002.

Thanks,
Amit

[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ac3ef7
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=f1b4c77

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

#264Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#263)
Re: Declarative partitioning - another take

On Mon, Jan 16, 2017 at 4:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The problem is that whereas the SlotValueDescription that we build to show
in the error message should be based on the tuple that was passed to
ExecInsert() or whatever NextCopyFrom() returned for CopyFrom() to
process, it might fail to be the case if the tuple was needed to be
converted after tuple routing. slot (the tuple in it and its tuple
descriptor) and resultRelInfo that ExecConstraint() receives *do*
correspond with each other, even after possible tuple conversion following
tuple-routing, and hence constraint checking itself works fine (since
commit 2ac3ef7a01 [1]). As said, it's the val_desc built to show in the
error message being based on the converted-for-partition tuple that could
be seen as a problem - is it acceptable if we showed in the error message
whatever the converted-for-partition tuple looks like which might have
columns ordered differently from the root table? If so, we could simply
forget the whole thing, including reverting f1b4c771 [2].

An example:

create table p (a int, b char, c int) partition by list (a);
create table p1 (b char, c int, a int); -- note the column order
alter table p attach partition p1 for values in (1);
alter table p add constraint check_b check (b = 'x');

insert into p values (1, 'y', 1);
ERROR: new row for relation "p1" violates check constraint "check_b"
DETAIL: Failing row contains (y, 1, 1).

Note that "(y, 1, 1)" results from using p1's descriptor on the converted
tuple. As long that's clear and acceptable, I think we need not worry
about this patch and revert the previously committed patch for this "problem".

Hmm. It would be fine, IMHO, if the detail message looked like the
one that BuildIndexValueDescription produces. Without the column
names, the clarity is somewhat lessened.

Anybody else have an opinion on this?

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

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

#265Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#260)
Re: Declarative partitioning - another take

On Tue, Jan 10, 2017 at 6:06 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ updated patches ]

I committed 0004 and also fixed the related regression test not to
rely on DROP .. CASCADE, which isn't always stable. The remainder of
this patch set needs a rebase, and perhaps you could also fold in
other pending partitioning fixes so I have everything to look at it in
one place.

Thanks.

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

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

#266Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#265)
Re: Declarative partitioning - another take

On Wed, Jan 18, 2017 at 3:12 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Jan 10, 2017 at 6:06 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ updated patches ]

I committed 0004 and also fixed the related regression test not to
rely on DROP .. CASCADE, which isn't always stable. The remainder of
this patch set needs a rebase, and perhaps you could also fold in
other pending partitioning fixes so I have everything to look at it in
one place.

Just to be a little more clear, I don't mind multiple threads each
with a patch or patch set so much, but multiple patch sets on the same
thread gets hard for me to track. Sorry for the inconvenience.

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

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

#267Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#266)
8 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/19 5:29, Robert Haas wrote:

On Wed, Jan 18, 2017 at 3:12 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Jan 10, 2017 at 6:06 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ updated patches ]

I committed 0004 and also fixed the related regression test not to
rely on DROP .. CASCADE, which isn't always stable. The remainder of

Thanks!

this patch set needs a rebase, and perhaps you could also fold in
other pending partitioning fixes so I have everything to look at it in
one place.

Just to be a little more clear, I don't mind multiple threads each
with a patch or patch set so much, but multiple patch sets on the same
thread gets hard for me to track. Sorry for the inconvenience.

OK, I agree that having multiple patch sets on the same thread is cumbersome.

So, here are all the patches I posted to date (and one new at the bottom)
for reported and unreported bugs, excluding the one involving
BulkInsertState for which you replied in a new thread.

I'll describe the attached patches in brief:

0001-Fix-a-bug-of-insertion-into-an-internal-partition.patch

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patch

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels. If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Last posted here:
/messages/by-id/99fbfad6-b89b-9427-a6ca-197aad98c48e@lab.ntt.co.jp

0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patch

In ExecInsert(), do not switch back to the root partitioned table
ResultRelInfo until after we finish ExecProcessReturning(), so that
RETURNING projection is done using the partition's descriptor. For
the projection to work correctly, we must initialize the same for
each leaf partition during ModifyTableState initialization.

0004-Fix-some-issues-with-views-and-partitioned-tables.patch

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patch

Because a given range bound in the PartitionBoundInfo.datums array
is sometimes a range lower bound and upper bound at other times, we
must be careful when assuming which, especially when interpreting
the result of partition_bound_bsearch which returns the index of the
greatest bound that is less than or equal to probe. Due to an error
in thinking about the same, the relevant code in
check_new_partition_bound() caused invalid partition (index = -1)
to be chosen as the partition being overlapped.

Last posted here:
/messages/by-id/603acb8b-5dec-31e8-29b0-609a68aac50f@lab.ntt.co.jp

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patch

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful. Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

0007-Avoid-code-duplication-in-map_partition_varattnos.patch

Code to map attribute numbers in map_partition_varattnos() duplicates
what convert_tuples_by_name_map() does. Avoid that as pointed out by
Álvaro Herrera.

Last posted here:
/messages/by-id/9ce97382-54c8-deb3-9ee9-a2ec271d866b@lab.ntt.co.jp

0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patch

This is the new one. There were quite a few commits recently to fix the
breakage in regression tests due to not using ORDER BY in queries on
system catalogs and using DROP TABLE ... CASCADE. There were still some
instances of the latter in create_table.sql and alter_table.sql.

Thanks,
Amit

Attachments:

0001-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0001-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 37c861d5eae1c8f11b3fae93cb209da262a13fbc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 1/8] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 42 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 1fd2162794..75386212e0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2432,7 +2432,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e633a50dd2..06e43cbb3a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,7 +1324,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ff277d300a..5457f8fbde 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,13 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1327,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3132,7 +3157,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d424031676..eb180fdb63 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 0382560d39..729d9ebbbc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -358,5 +358,11 @@ alter table p add constraint check_b check (b = 3);
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 2).
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p, p1, p11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a6eab8f365..5509555fc5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -221,5 +221,10 @@ alter table p add constraint check_b check (b = 3);
 -- after "(1, 2)" is routed to it
 insert into p values (1, 2);
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p, p1, p11;
-- 
2.11.0

0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From 1a47d44891c50e406fb3e1d9ca4bc0f536782a2a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 2/8] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels.  If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 29 ++++++++++++++++++++++-------
 src/backend/executor/execMain.c      |  2 --
 src/test/regress/expected/insert.out |  2 +-
 src/test/regress/sql/insert.sql      |  2 +-
 4 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 874e69d8d6..e540fc16d0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1642,7 +1642,10 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	bool		isnull[PARTITION_MAX_KEYS];
 	int			cur_offset,
 				cur_index;
-	int			i;
+	int			i,
+				result;
+	ExprContext *ecxt = GetPerTupleExprContext(estate);
+	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
 	/* start with the root partitioned table */
 	parent = pd[0];
@@ -1671,7 +1674,14 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			slot = myslot;
 		}
 
-		/* Extract partition key from tuple */
+		/*
+		 * Extract partition key from tuple; FormPartitionKeyDatum() expects
+		 * ecxt_scantuple to point to the correct tuple slot (which might be
+		 * different from the slot we received from the caller if the
+		 * partitioned table of the current level has different tuple
+		 * descriptor from its parent).
+		 */
+		ecxt->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
@@ -1726,16 +1736,21 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
+			result = -1;
 			*failed_at = RelationGetRelid(parent->reldesc);
-			return -1;
+			break;
 		}
-		else if (parent->indexes[cur_index] < 0)
-			parent = pd[-parent->indexes[cur_index]];
-		else
+		else if (parent->indexes[cur_index] >= 0)
+		{
+			result = parent->indexes[cur_index];
 			break;
+		}
+		else
+			parent = pd[-parent->indexes[cur_index]];
 	}
 
-	return parent->indexes[cur_index];
+	ecxt->ecxt_scantuple = ecxt_scantuple_old;
+	return result;
 }
 
 /*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5457f8fbde..67e46729f3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3191,9 +3191,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 729d9ebbbc..b8e74caee8 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -320,7 +320,7 @@ drop table part_ee_ff, part_gg2_2, part_gg2_1, part_gg2, part_gg1, part_gg;
 drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 5509555fc5..2154d01c56 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -193,7 +193,7 @@ drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchtext/x-diff; name=0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchDownload
From 365145d11c28c63106f641995ed8c266b7e05184 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 27 Dec 2016 16:56:58 +0900
Subject: [PATCH 3/8] Fix RETURNING to work correctly after tuple-routing

In ExecInsert(), do not switch back to the root partitioned table
ResultRelInfo until after we finish ExecProcessReturning(), so that
RETURNING projection is done using the partition's descriptor.  For
the projection to work correctly, we must initialize the same for
each leaf partition during ModifyTableState initialization.

With this commit, map_partition_varattnos() now accepts one more
argument viz. target_varno.  Previously, it assumed varno = 1 for
its input expressions, which was fine since its usage was limited to
partition constraints.  To use it with expressions such as an INSERT
statement's returning list, we must be prepared for varnos != 1 as
in the change above.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c        |  8 ++++---
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        |  4 ++--
 src/backend/executor/nodeModifyTable.c | 43 +++++++++++++++++++++++++++-------
 src/include/catalog/partition.h        |  3 ++-
 src/test/regress/expected/insert.out   | 24 ++++++++++++++++++-
 src/test/regress/sql/insert.sql        | 16 ++++++++++++-
 7 files changed, 82 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e540fc16d0..c0a5f98f3c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -883,7 +883,8 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * different from the parent's.
  */
 List *
-map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent)
 {
 	TupleDesc	tupdesc = RelationGetDescr(parent);
 	AttrNumber	attno;
@@ -908,7 +909,7 @@ map_partition_varattnos(List *expr, Relation partrel, Relation parent)
 	}
 
 	expr = (List *) map_variable_attnos((Node *) expr,
-										1, 0,
+										target_varno, 0,
 										part_attnos,
 										tupdesc->natts,
 										&found_whole_row);
@@ -1540,8 +1541,9 @@ generate_partition_qual(Relation rel)
 	 * Change Vars to have partition's attnos instead of the parent's.
 	 * We do this after we concatenate the parent's quals, because
 	 * we want every Var in it to bear this relation's attnos.
+	 * It's safe to assume varno = 1 here.
 	 */
-	result = map_partition_varattnos(result, rel, parent);
+	result = map_partition_varattnos(result, 1, rel, parent);
 
 	/* Save a copy in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 06e43cbb3a..18cac9ad2d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13454,6 +13454,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			constr = linitial(partConstraint);
 			my_constr = make_ands_implicit((Expr *) constr);
 			tab->partition_constraint = map_partition_varattnos(my_constr,
+																1,
 																part_rel,
 																rel);
 			/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67e46729f3..8cb9691056 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1279,10 +1279,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		/*
 		 * This is not our own partition constraint, but rather an ancestor's.
 		 * So any Vars in it bear the ancestor's attribute numbers.  We must
-		 * switch them to our own.
+		 * switch them to our own. (dummy varno = 1)
 		 */
 		if (partition_check != NIL)
-			partition_check = map_partition_varattnos(partition_check,
+			partition_check = map_partition_varattnos(partition_check, 1,
 													  resultRelationDesc,
 													  partition_root);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4692427e60..982f15d490 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,8 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = slot;
+	TupleTableSlot *oldslot = slot,
+				   *result = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -574,12 +575,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
-	if (saved_resultRelInfo)
-	{
-		resultRelInfo = saved_resultRelInfo;
-		estate->es_result_relation_info = resultRelInfo;
-	}
-
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -597,9 +592,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
-		return ExecProcessReturning(resultRelInfo, slot, planSlot);
+		result = ExecProcessReturning(resultRelInfo, slot, planSlot);
 
-	return NULL;
+	if (saved_resultRelInfo)
+		estate->es_result_relation_info = saved_resultRelInfo;
+
+	return result;
 }
 
 /* ----------------------------------------------------------------
@@ -1786,6 +1784,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		TupleTableSlot *slot;
 		ExprContext *econtext;
+		List		*returningList;
 
 		/*
 		 * Initialize result tuple slot and assign its rowtype using the first
@@ -1818,6 +1817,32 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
+
+		/*
+		 * Build a projection for each leaf partition rel.  Note that we
+		 * didn't build the returningList for each partition within the
+		 * planner, but simple translation of the varattnos for each
+		 * partition will suffice.  This only occurs for the INSERT case;
+		 * UPDATE/DELETE are handled above.
+		 */
+		resultRelInfo = mtstate->mt_partitions;
+		returningList = linitial(node->returningLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *rlist,
+					   *rliststate;
+
+			/* varno = node->nominalRelation */
+			rlist = map_partition_varattnos(returningList,
+											node->nominalRelation,
+											partrel, rel);
+			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+			resultRelInfo->ri_projectReturning =
+				ExecBuildProjectionInfo(rliststate, econtext, slot,
+									 resultRelInfo->ri_RelationDesc->rd_att);
+			resultRelInfo++;
+		}
 	}
 	else
 	{
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 537f0aad67..df7dcce331 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b8e74caee8..81af3ef497 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -364,5 +364,27 @@ DETAIL:  Failing row contains (1, 2).
 insert into p1 (a, b) values (2, 3);
 ERROR:  new row for relation "p11" violates partition constraint
 DETAIL:  Failing row contains (3, 2).
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+  a  | b | min | max 
+-----+---+-----+-----
+ p11 | 1 |   2 |   4
+ p12 | 1 |   5 |   9
+ p2  | 1 |  10 |  19
+ p3  | 1 |  20 |  29
+ p4  | 1 |  30 |  39
+(5 rows)
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 2154d01c56..454e1ce2e7 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -226,5 +226,19 @@ insert into p values (1, 2);
 -- selected by tuple-routing
 insert into p1 (a, b) values (2, 3);
 
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
-- 
2.11.0

0004-Fix-some-issues-with-views-and-partitioned-tables.patchtext/x-diff; name=0004-Fix-some-issues-with-views-and-partitioned-tables.patchDownload
From 719954a22d7a1fdf75fdcc10a51ea9fdf2d0ed38 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 15:33:02 +0900
Subject: [PATCH 4/8] Fix some issues with views and partitioned tables

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/executor/execMain.c               | 49 +++++++++++++++++++++++++++
 src/backend/executor/nodeModifyTable.c        | 40 ++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c          |  3 +-
 src/test/regress/expected/updatable_views.out | 24 +++++++++++++
 src/test/regress/sql/updatable_views.sql      | 19 +++++++++++
 5 files changed, 134 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8cb9691056..04c026f3f8 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1963,6 +1963,55 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 			Bitmapset  *insertedCols;
 			Bitmapset  *updatedCols;
 
+			/*
+			 * In case where the tuple is routed, it's been converted
+			 * to the partition's rowtype, which might differ from the
+			 * root table's.  We must convert it back to the root table's
+			 * type so that it's shown correctly in the error message.
+			 */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TupleDesc	old_tupdesc = RelationGetDescr(rel);
+				TupleConversionMap	*map;
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				/* a reverse map */
+				map = convert_tuples_by_name(old_tupdesc, tupdesc, false,
+								gettext_noop("could not convert row type"));
+				if (map != NULL)
+				{
+					tuple = do_convert_tuple(tuple, map);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
+			}
+
+			/*
+			 * In the case where the tuple is routed, it's been converted to
+			 * the partition's rowtype, which might differ from the root
+			 * table's.  We must convert it back to the root table's type so
+			 * that val_desc shown error message matches the input tuple.
+			 */
+			if (resultRelInfo->ri_PartitionRoot)
+			{
+				HeapTuple	tuple = ExecFetchSlotTuple(slot);
+				TupleDesc	old_tupdesc = RelationGetDescr(rel);
+				TupleConversionMap	*map;
+
+				rel = resultRelInfo->ri_PartitionRoot;
+				tupdesc = RelationGetDescr(rel);
+				/* a reverse map */
+				map = convert_tuples_by_name(old_tupdesc, tupdesc, false,
+							gettext_noop("could not convert row type"));
+				if (map != NULL)
+				{
+					tuple = do_convert_tuple(tuple, map);
+					ExecSetSlotDescriptor(slot, tupdesc);
+					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+				}
+			}
+
 			switch (wco->kind)
 			{
 					/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 982f15d490..57edfeab95 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1778,6 +1778,46 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * Build WITH CHECK OPTION constraints for each leaf partition rel.
+	 * Note that we didn't build the withCheckOptionList for each partition
+	 * within the planner, but simple translation of the varattnos for each
+	 * partition will suffice.  This only occurs for the INSERT case;
+	 * UPDATE/DELETE cases are handled above.
+	 */
+	if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0)
+	{
+		List		*wcoList;
+
+		Assert(operation == CMD_INSERT);
+		resultRelInfo = mtstate->mt_partitions;
+		wcoList = linitial(node->withCheckOptionLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *mapped_wcoList;
+			List	   *wcoExprs = NIL;
+			ListCell   *ll;
+
+			/* varno = node->nominalRelation */
+			mapped_wcoList = map_partition_varattnos(wcoList,
+													 node->nominalRelation,
+													 partrel, rel);
+			foreach(ll, mapped_wcoList)
+			{
+				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+				ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+											   mtstate->mt_plans[i]);
+
+				wcoExprs = lappend(wcoExprs, wcoExpr);
+			}
+
+			resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
+			resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+			resultRelInfo++;
+		}
+	}
+
+	/*
 	 * Initialize RETURNING projections if needed.
 	 */
 	if (node->returningLists)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d1ff3b20b6..d3e44fb135 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2249,7 +2249,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	if (base_rte->rtekind != RTE_RELATION ||
 		(base_rte->relkind != RELKIND_RELATION &&
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
-		 base_rte->relkind != RELKIND_VIEW))
+		 base_rte->relkind != RELKIND_VIEW &&
+		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
 
 	if (base_rte->tablesample)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 2da3c069e1..b49fd58ac9 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2367,3 +2367,27 @@ ERROR:  new row violates check option for view "v1"
 DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+ tableoid | a | b 
+----------+---+---
+ p11      | 1 | 2
+(1 row)
+
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+ERROR:  new row violates check option for view "pv_wco"
+DETAIL:  Failing row contains (1, 2).
+drop view pv, pv_wco;
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index ffc64d2de9..3c19edc8f7 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1112,3 +1112,22 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
 
 DROP VIEW v1;
 DROP TABLE t1;
+
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+drop view pv, pv_wco;
+drop table p, p1, p11;
-- 
2.11.0

0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patchtext/x-diff; name=0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patchDownload
From b8ea716792e382ae79a400d976d4ca224dae37e3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Jan 2017 17:43:36 +0900
Subject: [PATCH 5/8] Fix some wrong thinking in check_new_partition_bound()

Because a given range bound in the PartitionBoundInfo.datums array
is sometimes a range lower bound and upper bound at other times, we
must be careful when assuming which, especially when interpreting
the result of partition_bound_bsearch which returns the index of the
greatest bound that is less than or equal to probe.  Due to an error
in thinking about the same, the relevant code in
check_new_partition_bound() caused invalid partition (index = -1)
to be chosen as the partition being overlapped.

Also, we need not continue searching for even greater bound in
partition_bound_bsearch() once we find the first bound that is *equal*
to the probe, because we don't have duplicate datums.  That spends
cycles needlessly.  Per suggestion from Amul Sul.

Reported by: Amul Sul
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAAJ_b94XgbqVoXMyxxs63CaqWoMS1o2gpHiU0F7yGnJBnvDc_A%40mail.gmail.com
---
 src/backend/catalog/partition.c            | 62 ++++++++++++++++++++++--------
 src/test/regress/expected/create_table.out | 10 ++++-
 src/test/regress/sql/create_table.sql      |  4 ++
 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c0a5f98f3c..5ca57f44fb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -741,35 +741,64 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
 
 					/*
-					 * Find the greatest index of a range bound that is less
-					 * than or equal with the new lower bound.
+					 * Firstly, find the greatest range bound that is less
+					 * than or equal to the new lower bound.
 					 */
 					off1 = partition_bound_bsearch(key, boundinfo, lower, true,
 												   &equal);
 
 					/*
-					 * If equal has been set to true, that means the new lower
-					 * bound is found to be equal with the bound at off1,
-					 * which clearly means an overlap with the partition at
-					 * index off1+1).
-					 *
-					 * Otherwise, check if there is a "gap" that could be
-					 * occupied by the new partition.  In case of a gap, the
-					 * new upper bound should not cross past the upper
-					 * boundary of the gap, that is, off2 == off1 should be
-					 * true.
+					 * off1 == -1 means that all existing bounds are greater
+					 * than the new lower bound.  In that case and the case
+					 * where no partition is defined between the bounds at
+					 * off1 and off1 + 1, we have a "gap" in the range that
+					 * could be occupied by the new partition.  We confirm if
+					 * so by checking whether the new upper bound is confined
+					 * within the gap.
 					 */
 					if (!equal && boundinfo->indexes[off1 + 1] < 0)
 					{
 						off2 = partition_bound_bsearch(key, boundinfo, upper,
 													   true, &equal);
 
+						/*
+						 * If the new upper bound is returned to be equal to
+						 * the bound at off2, the latter must be the upper
+						 * bound of some partition with which the new partition
+						 * clearly overlaps.
+						 *
+						 * Also, if bound at off2 is not same as the one
+						 * returned for the new lower bound (IOW,
+						 * off1 != off2), then the new partition overlaps at
+						 * least one partition.
+						 */
 						if (equal || off1 != off2)
 						{
 							overlap = true;
-							with = boundinfo->indexes[off2 + 1];
+							/*
+							 * The bound at off2 could be the lower bound of
+							 * the partition with which the new partition
+							 * overlaps.  In that case, use the upper bound
+							 * (that is, the bound at off2 + 1) to get the
+							 * index of that partition.
+							 */
+							if (boundinfo->indexes[off2] < 0)
+								with = boundinfo->indexes[off2 + 1];
+							else
+								with = boundinfo->indexes[off2];
 						}
 					}
+					/*
+					 * If equal has been set to true or if there is no "gap"
+					 * between the bound at off1 and that at off1 + 1, the new
+					 * partition will overlap some partition.  In the former
+					 * case, the new lower bound is found to be equal to the
+					 * bound at off1, which could only ever be true if the
+					 * latter is the lower bound of some partition.  It's
+					 * clear in such a case that the new partition overlaps
+					 * that partition, whose index we get using its upper
+					 * bound (that is, using the bound at off1 + 1).
+					 */
 					else
 					{
 						overlap = true;
@@ -1972,8 +2001,8 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 }
 
 /*
- * Binary search on a collection of partition bounds. Returns greatest index
- * of bound in array boundinfo->datums which is less or equal with *probe.
+ * Binary search on a collection of partition bounds. Returns greatest
+ * bound in array boundinfo->datums which is less than or equal to *probe
  * If all bounds in the array are greater than *probe, -1 is returned.
  *
  * *probe could either be a partition bound or a Datum array representing
@@ -2005,6 +2034,9 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 		{
 			lo = mid;
 			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
 		}
 		else
 			hi = mid - 1;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 8a2818bf97..37edd6cde8 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -551,6 +551,12 @@ ERROR:  partition "fail_part" would overlap partition "part0"
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
 ERROR:  partition "fail_part" would overlap partition "part1"
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+ERROR:  partition "fail_part" would overlap partition "part3"
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -655,13 +661,15 @@ table part_c depends on table parted
 table part_c_1_10 depends on table part_c
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-NOTICE:  drop cascades to 14 other objects
+NOTICE:  drop cascades to 16 other objects
 DETAIL:  drop cascades to table part00
 drop cascades to table part10
 drop cascades to table part11
 drop cascades to table part12
 drop cascades to table part0
 drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
 drop cascades to table part_null_z
 drop cascades to table part_ab
 drop cascades to table part_1
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2ab652a055..f960d36fd0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -519,6 +519,10 @@ CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1)
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
 
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
-- 
2.11.0

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0006-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From 55ce468f608c27617ed0fe32a6874667266bdea4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 6/8] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 8 ++++++--
 src/backend/catalog/partition.c        | 5 ++---
 src/backend/commands/analyze.c         | 1 +
 src/backend/executor/execMain.c        | 1 +
 src/backend/executor/execQual.c        | 2 +-
 src/include/access/tupconvert.h        | 1 +
 6 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b17ceafa6e..bbbd271e1f 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -202,6 +202,7 @@ convert_tuples_by_position(TupleDesc indesc,
 TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg)
 {
 	TupleConversionMap *map;
@@ -216,11 +217,14 @@ convert_tuples_by_name(TupleDesc indesc,
 	/*
 	 * Check to see if the map is one-to-one and the tuple types are the same.
 	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * to inject the right OID into the tuple datum.  In the partitioning
+	 * case (!consider_typeid), tdhasoids must always match between indesc
+	 * and outdesc, so we need not require tdtypeid's to be the same.)
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		(!consider_typeid || indesc->tdtypeid == outdesc->tdtypeid))
 	{
+		Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);
 		same = true;
 		for (i = 0; i < n; i++)
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5ca57f44fb..75ec746cb4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1084,7 +1084,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			 */
 			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
 			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
-												   tupdesc,
+												   tupdesc, false,
 								gettext_noop("could not convert row type"));
 		}
 		else
@@ -1694,12 +1694,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e3e1a53072..31bec9f961 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1419,6 +1419,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 					map = convert_tuples_by_name(RelationGetDescr(childrel),
 												 RelationGetDescr(onerel),
+												 true,
 								 gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 04c026f3f8..b14cc98caf 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3201,6 +3201,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		 * partition from the parent's type to the partition's.
 		 */
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+													 false,
 								 gettext_noop("could not convert row type"));
 
 		InitResultRelInfo(leaf_part_rri,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index eed7e95c75..5f637006d1 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2920,7 +2920,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 
 		/* prepare map from old to new attribute numbers */
 		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
+											 cstate->outdesc, true,
 								 gettext_noop("could not convert row type"));
 		cstate->initialized = true;
 
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index e86cfd56c8..231fe872d7 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -36,6 +36,7 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg);
 
 extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc,
-- 
2.11.0

0007-Avoid-code-duplication-in-map_partition_varattnos.patchtext/x-diff; name=0007-Avoid-code-duplication-in-map_partition_varattnos.patchDownload
From a16dfe0603ee94a0ba8234f256ec39c7944cd31f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 16 Jan 2017 14:37:50 +0900
Subject: [PATCH 7/8] Avoid code duplication in map_partition_varattnos()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Code to map attribute numbers in map_partition_varattnos() duplicates
what convert_tuples_by_name_map() does.  Avoid that.

Amit Langote, pointed out by Álvaro Herrera.
---
 src/backend/catalog/partition.c | 21 ++++-----------------
 1 file changed, 4 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 75ec746cb4..a49a2110db 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -915,32 +915,19 @@ List *
 map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent)
 {
-	TupleDesc	tupdesc = RelationGetDescr(parent);
-	AttrNumber	attno;
 	AttrNumber *part_attnos;
 	bool		found_whole_row;
 
 	if (expr == NIL)
 		return NIL;
 
-	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
-	for (attno = 1; attno <= tupdesc->natts; attno++)
-	{
-		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
-		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	part_attno;
-
-		if (attribute->attisdropped)
-			continue;
-
-		part_attno = get_attnum(RelationGetRelid(partrel), attname);
-		part_attnos[attno - 1] = part_attno;
-	}
-
+	part_attnos = convert_tuples_by_name_map(RelationGetDescr(partrel),
+											 RelationGetDescr(parent),
+								 gettext_noop("could not convert row type"));
 	expr = (List *) map_variable_attnos((Node *) expr,
 										target_varno, 0,
 										part_attnos,
-										tupdesc->natts,
+										RelationGetDescr(parent)->natts,
 										&found_whole_row);
 	/* There can never be a whole-row reference here */
 	if (found_whole_row)
-- 
2.11.0

0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patchtext/x-diff; name=0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patchDownload
From fcb6e9ba1b9bea1e9d0ca362014fa1af78fca452 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 19 Jan 2017 13:35:18 +0900
Subject: [PATCH 8/8] Avoid DROP TABLE ... CASCADE in more partitioning tests

The output produced by specifying CASCADE is unreliable and causes
test failures occasionally.  This time, fix create_table.sql and
alter_table.sql to do that.

Patch by: Amit Langote
---
 src/test/regress/expected/alter_table.out  | 18 ++++++----------
 src/test/regress/expected/create_table.out | 34 ++++++------------------------
 src/test/regress/sql/alter_table.sql       | 10 +++++----
 src/test/regress/sql/create_table.sql      | 14 ++++++------
 4 files changed, 26 insertions(+), 50 deletions(-)

diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c510761c68..76b9c2d372 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3330,15 +3330,10 @@ ALTER TABLE list_parted2 DROP COLUMN b;
 ERROR:  cannot drop column named in partition key
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
--- cleanup
-DROP TABLE list_parted, list_parted2, range_parted CASCADE;
-NOTICE:  drop cascades to 6 other objects
-DETAIL:  drop cascades to table part1
-drop cascades to table part2
-drop cascades to table part_2
-drop cascades to table part_5
-drop cascades to table part_5_a
-drop cascades to table part_1
+-- cleanup: avoid using CASCADE
+DROP TABLE list_parted, part_1;
+DROP TABLE list_parted2, part_2, part_5, part_5_a;
+DROP TABLE range_parted, part1, part2;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
@@ -3367,6 +3362,5 @@ insert into p1 (a, b) values (2, 3);
 -- check that partition validation scan correctly detects violating rows
 alter table p attach partition p1 for values from (1, 2) to (1, 10);
 ERROR:  partition constraint is violated by some row
--- cleanup
-drop table p, p1 cascade;
-NOTICE:  drop cascades to table p11
+-- cleanup: avoid using CASCADE
+drop table p, p1, p11;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 37edd6cde8..6caa9c2407 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -651,30 +651,10 @@ Check constraints:
     "check_a" CHECK (length(a) > 0)
 Number of partitions: 3 (Use \d+ to list them.)
 
--- partitions cannot be dropped directly
-DROP TABLE part_a;
--- need to specify CASCADE to drop partitions along with the parent
-DROP TABLE parted;
-ERROR:  cannot drop table parted because other objects depend on it
-DETAIL:  table part_b depends on table parted
-table part_c depends on table parted
-table part_c_1_10 depends on table part_c
-HINT:  Use DROP ... CASCADE to drop the dependent objects too.
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-NOTICE:  drop cascades to 16 other objects
-DETAIL:  drop cascades to table part00
-drop cascades to table part10
-drop cascades to table part11
-drop cascades to table part12
-drop cascades to table part0
-drop cascades to table part1
-drop cascades to table part2
-drop cascades to table part3
-drop cascades to table part_null_z
-drop cascades to table part_ab
-drop cascades to table part_1
-drop cascades to table part_2
-drop cascades to table part_null
-drop cascades to table part_b
-drop cascades to table part_c
-drop cascades to table part_c_1_10
+-- cleanup: avoid using CASCADE
+DROP TABLE parted, part_a, part_b, part_c, part_c_1_10;
+DROP TABLE list_parted, part_1, part_2, part_null;
+DROP TABLE range_parted;
+DROP TABLE list_parted2, part_ab, part_null_z;
+DROP TABLE range_parted2, part0, part1, part2, part3;
+DROP TABLE range_parted3, part00, part10, part11, part12;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 86d27ac35c..4611cbb731 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2189,8 +2189,10 @@ ALTER TABLE part_2 INHERIT inh_test;
 ALTER TABLE list_parted2 DROP COLUMN b;
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
--- cleanup
-DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+-- cleanup: avoid using CASCADE
+DROP TABLE list_parted, part_1;
+DROP TABLE list_parted2, part_2, part_5, part_5_a;
+DROP TABLE range_parted, part1, part2;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
@@ -2215,5 +2217,5 @@ insert into p1 (a, b) values (2, 3);
 -- check that partition validation scan correctly detects violating rows
 alter table p attach partition p1 for values from (1, 2) to (1, 10);
 
--- cleanup
-drop table p, p1 cascade;
+-- cleanup: avoid using CASCADE
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f960d36fd0..8242e7328d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -585,10 +585,10 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 -- returned.
 \d parted
 
--- partitions cannot be dropped directly
-DROP TABLE part_a;
-
--- need to specify CASCADE to drop partitions along with the parent
-DROP TABLE parted;
-
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+-- cleanup: avoid using CASCADE
+DROP TABLE parted, part_a, part_b, part_c, part_c_1_10;
+DROP TABLE list_parted, part_1, part_2, part_null;
+DROP TABLE range_parted;
+DROP TABLE list_parted2, part_ab, part_null_z;
+DROP TABLE range_parted2, part0, part1, part2, part3;
+DROP TABLE range_parted3, part00, part10, part11, part12;
-- 
2.11.0

#268Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#267)
8 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/19 14:15, Amit Langote wrote:

So, here are all the patches I posted to date (and one new at the bottom)
for reported and unreported bugs, excluding the one involving
BulkInsertState for which you replied in a new thread.

I'll describe the attached patches in brief:

Sorry, I forgot to mention that I have skipped the patch I proposed to
modify the committed approach [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=f1b4c771ea74f42447dccaed42ffcdcccf3aa694 to get the correct tuple to show in the
constraint violation messages. It might be better to continue that
discussion at [2]/messages/by-id/CA+TgmoZjGzSM5WwnyapFaw3GxnDLWh7pm8Xiz8_QWQnUQy=SCA@mail.gmail.com.

And because I skipped that patch, I should have removed the related logic
in ExecWithCheckOptions() that I added in one of the later patches (patch
0004), which I forgot to do. So, here are all the patches again with the
correct 0004 this time. Sigh.

Thanks,
Amit

[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=f1b4c771ea74f42447dccaed42ffcdcccf3aa694
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=f1b4c771ea74f42447dccaed42ffcdcccf3aa694

[2]: /messages/by-id/CA+TgmoZjGzSM5WwnyapFaw3GxnDLWh7pm8Xiz8_QWQnUQy=SCA@mail.gmail.com
/messages/by-id/CA+TgmoZjGzSM5WwnyapFaw3GxnDLWh7pm8Xiz8_QWQnUQy=SCA@mail.gmail.com

Attachments:

0001-Fix-a-bug-of-insertion-into-an-internal-partition.patchtext/x-diff; name=0001-Fix-a-bug-of-insertion-into-an-internal-partition.patchDownload
From 37c861d5eae1c8f11b3fae93cb209da262a13fbc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 13 Dec 2016 15:07:41 +0900
Subject: [PATCH 1/8] Fix a bug of insertion into an internal partition.

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/commands/copy.c          |  1 -
 src/backend/commands/tablecmds.c     |  1 -
 src/backend/executor/execMain.c      | 42 ++++++++++++++++++++++++++++--------
 src/include/executor/executor.h      |  1 -
 src/test/regress/expected/insert.out |  6 ++++++
 src/test/regress/sql/insert.sql      |  5 +++++
 6 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 1fd2162794..75386212e0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2432,7 +2432,6 @@ CopyFrom(CopyState cstate)
 	InitResultRelInfo(resultRelInfo,
 					  cstate->rel,
 					  1,		/* dummy rangetable index */
-					  true,		/* do load partition check expression */
 					  NULL,
 					  0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e633a50dd2..06e43cbb3a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1324,7 +1324,6 @@ ExecuteTruncate(TruncateStmt *stmt)
 		InitResultRelInfo(resultRelInfo,
 						  rel,
 						  0,	/* dummy rangetable index */
-						  false,
 						  NULL,
 						  0);
 		resultRelInfo++;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ff277d300a..5457f8fbde 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -824,10 +824,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 			resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+
 			InitResultRelInfo(resultRelInfo,
 							  resultRelation,
 							  resultRelationIndex,
-							  true,
 							  NULL,
 							  estate->es_instrument);
 			resultRelInfo++;
@@ -1218,10 +1218,11 @@ void
 InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options)
 {
+	List   *partition_check = NIL;
+
 	MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
 	resultRelInfo->type = T_ResultRelInfo;
 	resultRelInfo->ri_RangeTableIndex = resultRelationIndex;
@@ -1257,13 +1258,38 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
-	if (load_partition_check)
-		resultRelInfo->ri_PartitionCheck =
-							RelationGetPartitionQual(resultRelationDesc);
+
 	/*
-	 * The following gets set to NULL unless we are initializing leaf
-	 * partitions for tuple-routing.
+	 * If partition_root has been specified, that means we are builiding the
+	 * ResultRelationInfo for one of its leaf partitions.  In that case, we
+	 * need *not* initialize the leaf partition's constraint, but rather the
+	 * the partition_root's (if any).  We must do that explicitly like this,
+	 * because implicit partition constraints are not inherited like user-
+	 * defined constraints and would fail to be enforced by ExecConstraints()
+	 * after a tuple is routed to a leaf partition.
 	 */
+	if (partition_root)
+	{
+		/*
+		 * Root table itself may or may not be a partition; partition_check
+		 * would be NIL in the latter case.
+		 */
+		partition_check = RelationGetPartitionQual(partition_root);
+
+		/*
+		 * This is not our own partition constraint, but rather an ancestor's.
+		 * So any Vars in it bear the ancestor's attribute numbers.  We must
+		 * switch them to our own.
+		 */
+		if (partition_check != NIL)
+			partition_check = map_partition_varattnos(partition_check,
+													  resultRelationDesc,
+													  partition_root);
+	}
+	else
+		partition_check = RelationGetPartitionQual(resultRelationDesc);
+
+	resultRelInfo->ri_PartitionCheck = partition_check;
 	resultRelInfo->ri_PartitionRoot = partition_root;
 }
 
@@ -1327,7 +1353,6 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 	InitResultRelInfo(rInfo,
 					  rel,
 					  0,		/* dummy rangetable index */
-					  true,
 					  NULL,
 					  estate->es_instrument);
 	estate->es_trig_target_relations =
@@ -3132,7 +3157,6 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
 						  1,	 /* dummy */
-						  false,
 						  rel,
 						  0);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d424031676..eb180fdb63 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -189,7 +189,6 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Relation resultRelationDesc,
 				  Index resultRelationIndex,
-				  bool load_partition_check,
 				  Relation partition_root,
 				  int instrument_options);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 0382560d39..729d9ebbbc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -358,5 +358,11 @@ alter table p add constraint check_b check (b = 3);
 insert into p values (1, 2);
 ERROR:  new row for relation "p11" violates check constraint "check_b"
 DETAIL:  Failing row contains (1, 2).
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+ERROR:  new row for relation "p11" violates partition constraint
+DETAIL:  Failing row contains (3, 2).
 -- cleanup
 drop table p, p1, p11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a6eab8f365..5509555fc5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -221,5 +221,10 @@ alter table p add constraint check_b check (b = 3);
 -- after "(1, 2)" is routed to it
 insert into p values (1, 2);
 
+-- check that inserting into an internal partition successfully results in
+-- checking its partition constraint before inserting into the leaf partition
+-- selected by tuple-routing
+insert into p1 (a, b) values (2, 3);
+
 -- cleanup
 drop table p, p1, p11;
-- 
2.11.0

0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From 1a47d44891c50e406fb3e1d9ca4bc0f536782a2a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 2/8] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels.  If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 29 ++++++++++++++++++++++-------
 src/backend/executor/execMain.c      |  2 --
 src/test/regress/expected/insert.out |  2 +-
 src/test/regress/sql/insert.sql      |  2 +-
 4 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 874e69d8d6..e540fc16d0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1642,7 +1642,10 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	bool		isnull[PARTITION_MAX_KEYS];
 	int			cur_offset,
 				cur_index;
-	int			i;
+	int			i,
+				result;
+	ExprContext *ecxt = GetPerTupleExprContext(estate);
+	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
 	/* start with the root partitioned table */
 	parent = pd[0];
@@ -1671,7 +1674,14 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			slot = myslot;
 		}
 
-		/* Extract partition key from tuple */
+		/*
+		 * Extract partition key from tuple; FormPartitionKeyDatum() expects
+		 * ecxt_scantuple to point to the correct tuple slot (which might be
+		 * different from the slot we received from the caller if the
+		 * partitioned table of the current level has different tuple
+		 * descriptor from its parent).
+		 */
+		ecxt->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
@@ -1726,16 +1736,21 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
+			result = -1;
 			*failed_at = RelationGetRelid(parent->reldesc);
-			return -1;
+			break;
 		}
-		else if (parent->indexes[cur_index] < 0)
-			parent = pd[-parent->indexes[cur_index]];
-		else
+		else if (parent->indexes[cur_index] >= 0)
+		{
+			result = parent->indexes[cur_index];
 			break;
+		}
+		else
+			parent = pd[-parent->indexes[cur_index]];
 	}
 
-	return parent->indexes[cur_index];
+	ecxt->ecxt_scantuple = ecxt_scantuple_old;
+	return result;
 }
 
 /*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5457f8fbde..67e46729f3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3191,9 +3191,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 729d9ebbbc..b8e74caee8 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -320,7 +320,7 @@ drop table part_ee_ff, part_gg2_2, part_gg2_1, part_gg2, part_gg1, part_gg;
 drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 5509555fc5..2154d01c56 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -193,7 +193,7 @@ drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchtext/x-diff; name=0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patchDownload
From 365145d11c28c63106f641995ed8c266b7e05184 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 27 Dec 2016 16:56:58 +0900
Subject: [PATCH 3/8] Fix RETURNING to work correctly after tuple-routing

In ExecInsert(), do not switch back to the root partitioned table
ResultRelInfo until after we finish ExecProcessReturning(), so that
RETURNING projection is done using the partition's descriptor.  For
the projection to work correctly, we must initialize the same for
each leaf partition during ModifyTableState initialization.

With this commit, map_partition_varattnos() now accepts one more
argument viz. target_varno.  Previously, it assumed varno = 1 for
its input expressions, which was fine since its usage was limited to
partition constraints.  To use it with expressions such as an INSERT
statement's returning list, we must be prepared for varnos != 1 as
in the change above.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/catalog/partition.c        |  8 ++++---
 src/backend/commands/tablecmds.c       |  1 +
 src/backend/executor/execMain.c        |  4 ++--
 src/backend/executor/nodeModifyTable.c | 43 +++++++++++++++++++++++++++-------
 src/include/catalog/partition.h        |  3 ++-
 src/test/regress/expected/insert.out   | 24 ++++++++++++++++++-
 src/test/regress/sql/insert.sql        | 16 ++++++++++++-
 7 files changed, 82 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e540fc16d0..c0a5f98f3c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -883,7 +883,8 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
  * different from the parent's.
  */
 List *
-map_partition_varattnos(List *expr, Relation partrel, Relation parent)
+map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent)
 {
 	TupleDesc	tupdesc = RelationGetDescr(parent);
 	AttrNumber	attno;
@@ -908,7 +909,7 @@ map_partition_varattnos(List *expr, Relation partrel, Relation parent)
 	}
 
 	expr = (List *) map_variable_attnos((Node *) expr,
-										1, 0,
+										target_varno, 0,
 										part_attnos,
 										tupdesc->natts,
 										&found_whole_row);
@@ -1540,8 +1541,9 @@ generate_partition_qual(Relation rel)
 	 * Change Vars to have partition's attnos instead of the parent's.
 	 * We do this after we concatenate the parent's quals, because
 	 * we want every Var in it to bear this relation's attnos.
+	 * It's safe to assume varno = 1 here.
 	 */
-	result = map_partition_varattnos(result, rel, parent);
+	result = map_partition_varattnos(result, 1, rel, parent);
 
 	/* Save a copy in the relcache */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 06e43cbb3a..18cac9ad2d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13454,6 +13454,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 			constr = linitial(partConstraint);
 			my_constr = make_ands_implicit((Expr *) constr);
 			tab->partition_constraint = map_partition_varattnos(my_constr,
+																1,
 																part_rel,
 																rel);
 			/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67e46729f3..8cb9691056 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1279,10 +1279,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		/*
 		 * This is not our own partition constraint, but rather an ancestor's.
 		 * So any Vars in it bear the ancestor's attribute numbers.  We must
-		 * switch them to our own.
+		 * switch them to our own. (dummy varno = 1)
 		 */
 		if (partition_check != NIL)
-			partition_check = map_partition_varattnos(partition_check,
+			partition_check = map_partition_varattnos(partition_check, 1,
 													  resultRelationDesc,
 													  partition_root);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4692427e60..982f15d490 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -262,7 +262,8 @@ ExecInsert(ModifyTableState *mtstate,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
-	TupleTableSlot *oldslot = slot;
+	TupleTableSlot *oldslot = slot,
+				   *result = NULL;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -574,12 +575,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 	list_free(recheckIndexes);
 
-	if (saved_resultRelInfo)
-	{
-		resultRelInfo = saved_resultRelInfo;
-		estate->es_result_relation_info = resultRelInfo;
-	}
-
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -597,9 +592,12 @@ ExecInsert(ModifyTableState *mtstate,
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
-		return ExecProcessReturning(resultRelInfo, slot, planSlot);
+		result = ExecProcessReturning(resultRelInfo, slot, planSlot);
 
-	return NULL;
+	if (saved_resultRelInfo)
+		estate->es_result_relation_info = saved_resultRelInfo;
+
+	return result;
 }
 
 /* ----------------------------------------------------------------
@@ -1786,6 +1784,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		TupleTableSlot *slot;
 		ExprContext *econtext;
+		List		*returningList;
 
 		/*
 		 * Initialize result tuple slot and assign its rowtype using the first
@@ -1818,6 +1817,32 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
+
+		/*
+		 * Build a projection for each leaf partition rel.  Note that we
+		 * didn't build the returningList for each partition within the
+		 * planner, but simple translation of the varattnos for each
+		 * partition will suffice.  This only occurs for the INSERT case;
+		 * UPDATE/DELETE are handled above.
+		 */
+		resultRelInfo = mtstate->mt_partitions;
+		returningList = linitial(node->returningLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *rlist,
+					   *rliststate;
+
+			/* varno = node->nominalRelation */
+			rlist = map_partition_varattnos(returningList,
+											node->nominalRelation,
+											partrel, rel);
+			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+			resultRelInfo->ri_projectReturning =
+				ExecBuildProjectionInfo(rliststate, econtext, slot,
+									 resultRelInfo->ri_RelationDesc->rd_att);
+			resultRelInfo++;
+		}
 	}
 	else
 	{
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 537f0aad67..df7dcce331 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,7 +77,8 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
-extern List *map_partition_varattnos(List *expr, Relation partrel, Relation parent);
+extern List *map_partition_varattnos(List *expr, int target_varno,
+						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 
 /* For tuple routing */
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index b8e74caee8..81af3ef497 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -364,5 +364,27 @@ DETAIL:  Failing row contains (1, 2).
 insert into p1 (a, b) values (2, 3);
 ERROR:  new row for relation "p11" violates partition constraint
 DETAIL:  Failing row contains (3, 2).
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+  a  | b | min | max 
+-----+---+-----+-----
+ p11 | 1 |   2 |   4
+ p12 | 1 |   5 |   9
+ p2  | 1 |  10 |  19
+ p3  | 1 |  20 |  29
+ p4  | 1 |  30 |  39
+(5 rows)
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 2154d01c56..454e1ce2e7 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -226,5 +226,19 @@ insert into p values (1, 2);
 -- selected by tuple-routing
 insert into p1 (a, b) values (2, 3);
 
+-- check that RETURNING works correctly with tuple-routing
+alter table p drop constraint check_b;
+create table p12 partition of p1 for values from (5) to (10);
+create table p2 (b int not null, a int not null);
+alter table p attach partition p2 for values from (1, 10) to (1, 20);
+create table p3 partition of p for values from (1, 20) to (1, 30);
+create table p4 (like p);
+alter table p4 drop a;
+alter table p4 add a int not null;
+alter table p attach partition p4 for values from (1, 30) to (1, 40);
+with ins (a, b, c) as
+  (insert into p (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
+  select a, b, min(c), max(c) from ins group by a, b order by 1;
+
 -- cleanup
-drop table p, p1, p11;
+drop table p, p1, p11, p12, p2, p3, p4;
-- 
2.11.0

0004-Fix-some-issues-with-views-and-partitioned-tables.patchtext/x-diff; name=0004-Fix-some-issues-with-views-and-partitioned-tables.patchDownload
From 25fb172bfd9c40c89c835e62610cbfb5852a9115 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 15:33:02 +0900
Subject: [PATCH 4/8] Fix some issues with views and partitioned tables

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/executor/nodeModifyTable.c        | 40 +++++++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c          |  3 +-
 src/test/regress/expected/updatable_views.out | 24 ++++++++++++++++
 src/test/regress/sql/updatable_views.sql      | 19 +++++++++++++
 4 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 982f15d490..57edfeab95 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1778,6 +1778,46 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * Build WITH CHECK OPTION constraints for each leaf partition rel.
+	 * Note that we didn't build the withCheckOptionList for each partition
+	 * within the planner, but simple translation of the varattnos for each
+	 * partition will suffice.  This only occurs for the INSERT case;
+	 * UPDATE/DELETE cases are handled above.
+	 */
+	if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0)
+	{
+		List		*wcoList;
+
+		Assert(operation == CMD_INSERT);
+		resultRelInfo = mtstate->mt_partitions;
+		wcoList = linitial(node->withCheckOptionLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *mapped_wcoList;
+			List	   *wcoExprs = NIL;
+			ListCell   *ll;
+
+			/* varno = node->nominalRelation */
+			mapped_wcoList = map_partition_varattnos(wcoList,
+													 node->nominalRelation,
+													 partrel, rel);
+			foreach(ll, mapped_wcoList)
+			{
+				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+				ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+											   mtstate->mt_plans[i]);
+
+				wcoExprs = lappend(wcoExprs, wcoExpr);
+			}
+
+			resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
+			resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+			resultRelInfo++;
+		}
+	}
+
+	/*
 	 * Initialize RETURNING projections if needed.
 	 */
 	if (node->returningLists)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d1ff3b20b6..d3e44fb135 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2249,7 +2249,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	if (base_rte->rtekind != RTE_RELATION ||
 		(base_rte->relkind != RELKIND_RELATION &&
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
-		 base_rte->relkind != RELKIND_VIEW))
+		 base_rte->relkind != RELKIND_VIEW &&
+		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
 
 	if (base_rte->tablesample)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 2da3c069e1..2ae3613cec 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2367,3 +2367,27 @@ ERROR:  new row violates check option for view "v1"
 DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+ tableoid | a | b 
+----------+---+---
+ p11      | 1 | 2
+(1 row)
+
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+ERROR:  new row violates check option for view "pv_wco"
+DETAIL:  Failing row contains (2, 1).
+drop view pv, pv_wco;
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index ffc64d2de9..3c19edc8f7 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1112,3 +1112,22 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
 
 DROP VIEW v1;
 DROP TABLE t1;
+
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+drop view pv, pv_wco;
+drop table p, p1, p11;
-- 
2.11.0

0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patchtext/x-diff; name=0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patchDownload
From 677f7cc8a7a337aee8baf11695783b28fc2308ba Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 10 Jan 2017 17:43:36 +0900
Subject: [PATCH 5/8] Fix some wrong thinking in check_new_partition_bound()

Because a given range bound in the PartitionBoundInfo.datums array
is sometimes a range lower bound and upper bound at other times, we
must be careful when assuming which, especially when interpreting
the result of partition_bound_bsearch which returns the index of the
greatest bound that is less than or equal to probe.  Due to an error
in thinking about the same, the relevant code in
check_new_partition_bound() caused invalid partition (index = -1)
to be chosen as the partition being overlapped.

Also, we need not continue searching for even greater bound in
partition_bound_bsearch() once we find the first bound that is *equal*
to the probe, because we don't have duplicate datums.  That spends
cycles needlessly.  Per suggestion from Amul Sul.

Reported by: Amul Sul
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAAJ_b94XgbqVoXMyxxs63CaqWoMS1o2gpHiU0F7yGnJBnvDc_A%40mail.gmail.com
---
 src/backend/catalog/partition.c            | 62 ++++++++++++++++++++++--------
 src/test/regress/expected/create_table.out | 10 ++++-
 src/test/regress/sql/create_table.sql      |  4 ++
 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c0a5f98f3c..5ca57f44fb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -741,35 +741,64 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
 
 					/*
-					 * Find the greatest index of a range bound that is less
-					 * than or equal with the new lower bound.
+					 * Firstly, find the greatest range bound that is less
+					 * than or equal to the new lower bound.
 					 */
 					off1 = partition_bound_bsearch(key, boundinfo, lower, true,
 												   &equal);
 
 					/*
-					 * If equal has been set to true, that means the new lower
-					 * bound is found to be equal with the bound at off1,
-					 * which clearly means an overlap with the partition at
-					 * index off1+1).
-					 *
-					 * Otherwise, check if there is a "gap" that could be
-					 * occupied by the new partition.  In case of a gap, the
-					 * new upper bound should not cross past the upper
-					 * boundary of the gap, that is, off2 == off1 should be
-					 * true.
+					 * off1 == -1 means that all existing bounds are greater
+					 * than the new lower bound.  In that case and the case
+					 * where no partition is defined between the bounds at
+					 * off1 and off1 + 1, we have a "gap" in the range that
+					 * could be occupied by the new partition.  We confirm if
+					 * so by checking whether the new upper bound is confined
+					 * within the gap.
 					 */
 					if (!equal && boundinfo->indexes[off1 + 1] < 0)
 					{
 						off2 = partition_bound_bsearch(key, boundinfo, upper,
 													   true, &equal);
 
+						/*
+						 * If the new upper bound is returned to be equal to
+						 * the bound at off2, the latter must be the upper
+						 * bound of some partition with which the new partition
+						 * clearly overlaps.
+						 *
+						 * Also, if bound at off2 is not same as the one
+						 * returned for the new lower bound (IOW,
+						 * off1 != off2), then the new partition overlaps at
+						 * least one partition.
+						 */
 						if (equal || off1 != off2)
 						{
 							overlap = true;
-							with = boundinfo->indexes[off2 + 1];
+							/*
+							 * The bound at off2 could be the lower bound of
+							 * the partition with which the new partition
+							 * overlaps.  In that case, use the upper bound
+							 * (that is, the bound at off2 + 1) to get the
+							 * index of that partition.
+							 */
+							if (boundinfo->indexes[off2] < 0)
+								with = boundinfo->indexes[off2 + 1];
+							else
+								with = boundinfo->indexes[off2];
 						}
 					}
+					/*
+					 * If equal has been set to true or if there is no "gap"
+					 * between the bound at off1 and that at off1 + 1, the new
+					 * partition will overlap some partition.  In the former
+					 * case, the new lower bound is found to be equal to the
+					 * bound at off1, which could only ever be true if the
+					 * latter is the lower bound of some partition.  It's
+					 * clear in such a case that the new partition overlaps
+					 * that partition, whose index we get using its upper
+					 * bound (that is, using the bound at off1 + 1).
+					 */
 					else
 					{
 						overlap = true;
@@ -1972,8 +2001,8 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
 }
 
 /*
- * Binary search on a collection of partition bounds. Returns greatest index
- * of bound in array boundinfo->datums which is less or equal with *probe.
+ * Binary search on a collection of partition bounds. Returns greatest
+ * bound in array boundinfo->datums which is less than or equal to *probe
  * If all bounds in the array are greater than *probe, -1 is returned.
  *
  * *probe could either be a partition bound or a Datum array representing
@@ -2005,6 +2034,9 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 		{
 			lo = mid;
 			*is_equal = (cmpval == 0);
+
+			if (*is_equal)
+				break;
 		}
 		else
 			hi = mid - 1;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 8a2818bf97..37edd6cde8 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -551,6 +551,12 @@ ERROR:  partition "fail_part" would overlap partition "part0"
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
 ERROR:  partition "fail_part" would overlap partition "part1"
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+ERROR:  partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+ERROR:  partition "fail_part" would overlap partition "part3"
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -655,13 +661,15 @@ table part_c depends on table parted
 table part_c_1_10 depends on table part_c
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-NOTICE:  drop cascades to 14 other objects
+NOTICE:  drop cascades to 16 other objects
 DETAIL:  drop cascades to table part00
 drop cascades to table part10
 drop cascades to table part11
 drop cascades to table part12
 drop cascades to table part0
 drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
 drop cascades to table part_null_z
 drop cascades to table part_ab
 drop cascades to table part_1
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 2ab652a055..f960d36fd0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -519,6 +519,10 @@ CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1)
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
 
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
-- 
2.11.0

0006-Avoid-code-duplication-in-map_partition_varattnos.patchtext/x-diff; name=0006-Avoid-code-duplication-in-map_partition_varattnos.patchDownload
From 230d8b1c96334d003b1e52da1843a7ecf61d9440 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 16 Jan 2017 14:37:50 +0900
Subject: [PATCH 6/8] Avoid code duplication in map_partition_varattnos()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Code to map attribute numbers in map_partition_varattnos() duplicates
what convert_tuples_by_name_map() does.  Avoid that.

Amit Langote, pointed out by Álvaro Herrera.
---
 src/backend/catalog/partition.c | 21 ++++-----------------
 1 file changed, 4 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5ca57f44fb..0a50808945 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -915,32 +915,19 @@ List *
 map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent)
 {
-	TupleDesc	tupdesc = RelationGetDescr(parent);
-	AttrNumber	attno;
 	AttrNumber *part_attnos;
 	bool		found_whole_row;
 
 	if (expr == NIL)
 		return NIL;
 
-	part_attnos = (AttrNumber *) palloc0(tupdesc->natts * sizeof(AttrNumber));
-	for (attno = 1; attno <= tupdesc->natts; attno++)
-	{
-		Form_pg_attribute attribute = tupdesc->attrs[attno - 1];
-		char	   *attname = NameStr(attribute->attname);
-		AttrNumber	part_attno;
-
-		if (attribute->attisdropped)
-			continue;
-
-		part_attno = get_attnum(RelationGetRelid(partrel), attname);
-		part_attnos[attno - 1] = part_attno;
-	}
-
+	part_attnos = convert_tuples_by_name_map(RelationGetDescr(partrel),
+											 RelationGetDescr(parent),
+								 gettext_noop("could not convert row type"));
 	expr = (List *) map_variable_attnos((Node *) expr,
 										target_varno, 0,
 										part_attnos,
-										tupdesc->natts,
+										RelationGetDescr(parent)->natts,
 										&found_whole_row);
 	/* There can never be a whole-row reference here */
 	if (found_whole_row)
-- 
2.11.0

0007-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0007-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From b4b131bcef2a239811e56491faebb199eeff859c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 7/8] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 8 ++++++--
 src/backend/catalog/partition.c        | 5 ++---
 src/backend/commands/analyze.c         | 1 +
 src/backend/executor/execMain.c        | 1 +
 src/backend/executor/execQual.c        | 2 +-
 src/include/access/tupconvert.h        | 1 +
 6 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b17ceafa6e..bbbd271e1f 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -202,6 +202,7 @@ convert_tuples_by_position(TupleDesc indesc,
 TupleConversionMap *
 convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg)
 {
 	TupleConversionMap *map;
@@ -216,11 +217,14 @@ convert_tuples_by_name(TupleDesc indesc,
 	/*
 	 * Check to see if the map is one-to-one and the tuple types are the same.
 	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * to inject the right OID into the tuple datum.  In the partitioning
+	 * case (!consider_typeid), tdhasoids must always match between indesc
+	 * and outdesc, so we need not require tdtypeid's to be the same.)
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		(!consider_typeid || indesc->tdtypeid == outdesc->tdtypeid))
 	{
+		Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);
 		same = true;
 		for (i = 0; i < n; i++)
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0a50808945..a49a2110db 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1071,7 +1071,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
 			 */
 			pd[i]->tupslot = MakeSingleTupleTableSlot(tupdesc);
 			pd[i]->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
-												   tupdesc,
+												   tupdesc, false,
 								gettext_noop("could not convert row type"));
 		}
 		else
@@ -1681,12 +1681,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e3e1a53072..31bec9f961 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1419,6 +1419,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 
 					map = convert_tuples_by_name(RelationGetDescr(childrel),
 												 RelationGetDescr(onerel),
+												 true,
 								 gettext_noop("could not convert row type"));
 					if (map != NULL)
 					{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8cb9691056..410f08a2c7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3152,6 +3152,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 		 * partition from the parent's type to the partition's.
 		 */
 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
+													 false,
 								 gettext_noop("could not convert row type"));
 
 		InitResultRelInfo(leaf_part_rri,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index eed7e95c75..5f637006d1 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2920,7 +2920,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 
 		/* prepare map from old to new attribute numbers */
 		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
+											 cstate->outdesc, true,
 								 gettext_noop("could not convert row type"));
 		cstate->initialized = true;
 
diff --git a/src/include/access/tupconvert.h b/src/include/access/tupconvert.h
index e86cfd56c8..231fe872d7 100644
--- a/src/include/access/tupconvert.h
+++ b/src/include/access/tupconvert.h
@@ -36,6 +36,7 @@ extern TupleConversionMap *convert_tuples_by_position(TupleDesc indesc,
 
 extern TupleConversionMap *convert_tuples_by_name(TupleDesc indesc,
 					   TupleDesc outdesc,
+					   bool consider_typeid,
 					   const char *msg);
 
 extern AttrNumber *convert_tuples_by_name_map(TupleDesc indesc,
-- 
2.11.0

0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patchtext/x-diff; name=0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patchDownload
From 1c9fc0d70a66ff3a167a6e2ab0f8a3b48d1d969d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 19 Jan 2017 13:35:18 +0900
Subject: [PATCH 8/8] Avoid DROP TABLE ... CASCADE in more partitioning tests

The output produced by specifying CASCADE is unreliable and causes
test failures occasionally.  This time, fix create_table.sql and
alter_table.sql to do that.

Patch by: Amit Langote
---
 src/test/regress/expected/alter_table.out  | 18 ++++++----------
 src/test/regress/expected/create_table.out | 34 ++++++------------------------
 src/test/regress/sql/alter_table.sql       | 10 +++++----
 src/test/regress/sql/create_table.sql      | 14 ++++++------
 4 files changed, 26 insertions(+), 50 deletions(-)

diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c510761c68..76b9c2d372 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3330,15 +3330,10 @@ ALTER TABLE list_parted2 DROP COLUMN b;
 ERROR:  cannot drop column named in partition key
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
--- cleanup
-DROP TABLE list_parted, list_parted2, range_parted CASCADE;
-NOTICE:  drop cascades to 6 other objects
-DETAIL:  drop cascades to table part1
-drop cascades to table part2
-drop cascades to table part_2
-drop cascades to table part_5
-drop cascades to table part_5_a
-drop cascades to table part_1
+-- cleanup: avoid using CASCADE
+DROP TABLE list_parted, part_1;
+DROP TABLE list_parted2, part_2, part_5, part_5_a;
+DROP TABLE range_parted, part1, part2;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
@@ -3367,6 +3362,5 @@ insert into p1 (a, b) values (2, 3);
 -- check that partition validation scan correctly detects violating rows
 alter table p attach partition p1 for values from (1, 2) to (1, 10);
 ERROR:  partition constraint is violated by some row
--- cleanup
-drop table p, p1 cascade;
-NOTICE:  drop cascades to table p11
+-- cleanup: avoid using CASCADE
+drop table p, p1, p11;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 37edd6cde8..6caa9c2407 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -651,30 +651,10 @@ Check constraints:
     "check_a" CHECK (length(a) > 0)
 Number of partitions: 3 (Use \d+ to list them.)
 
--- partitions cannot be dropped directly
-DROP TABLE part_a;
--- need to specify CASCADE to drop partitions along with the parent
-DROP TABLE parted;
-ERROR:  cannot drop table parted because other objects depend on it
-DETAIL:  table part_b depends on table parted
-table part_c depends on table parted
-table part_c_1_10 depends on table part_c
-HINT:  Use DROP ... CASCADE to drop the dependent objects too.
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-NOTICE:  drop cascades to 16 other objects
-DETAIL:  drop cascades to table part00
-drop cascades to table part10
-drop cascades to table part11
-drop cascades to table part12
-drop cascades to table part0
-drop cascades to table part1
-drop cascades to table part2
-drop cascades to table part3
-drop cascades to table part_null_z
-drop cascades to table part_ab
-drop cascades to table part_1
-drop cascades to table part_2
-drop cascades to table part_null
-drop cascades to table part_b
-drop cascades to table part_c
-drop cascades to table part_c_1_10
+-- cleanup: avoid using CASCADE
+DROP TABLE parted, part_a, part_b, part_c, part_c_1_10;
+DROP TABLE list_parted, part_1, part_2, part_null;
+DROP TABLE range_parted;
+DROP TABLE list_parted2, part_ab, part_null_z;
+DROP TABLE range_parted2, part0, part1, part2, part3;
+DROP TABLE range_parted3, part00, part10, part11, part12;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 86d27ac35c..4611cbb731 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2189,8 +2189,10 @@ ALTER TABLE part_2 INHERIT inh_test;
 ALTER TABLE list_parted2 DROP COLUMN b;
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
--- cleanup
-DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+-- cleanup: avoid using CASCADE
+DROP TABLE list_parted, part_1;
+DROP TABLE list_parted2, part_2, part_5, part_5_a;
+DROP TABLE range_parted, part1, part2;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
@@ -2215,5 +2217,5 @@ insert into p1 (a, b) values (2, 3);
 -- check that partition validation scan correctly detects violating rows
 alter table p attach partition p1 for values from (1, 2) to (1, 10);
 
--- cleanup
-drop table p, p1 cascade;
+-- cleanup: avoid using CASCADE
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f960d36fd0..8242e7328d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -585,10 +585,10 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 -- returned.
 \d parted
 
--- partitions cannot be dropped directly
-DROP TABLE part_a;
-
--- need to specify CASCADE to drop partitions along with the parent
-DROP TABLE parted;
-
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+-- cleanup: avoid using CASCADE
+DROP TABLE parted, part_a, part_b, part_c, part_c_1_10;
+DROP TABLE list_parted, part_1, part_2, part_null;
+DROP TABLE range_parted;
+DROP TABLE list_parted2, part_ab, part_null_z;
+DROP TABLE range_parted2, part0, part1, part2, part3;
+DROP TABLE range_parted3, part00, part10, part11, part12;
-- 
2.11.0

#269Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#267)
Re: Declarative partitioning - another take

On Thu, Jan 19, 2017 at 12:15 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

0001-Fix-a-bug-of-insertion-into-an-internal-partition.patch

Since implicit partition constraints are not inherited, an internal
partition's constraint was not being enforced when targeted directly.
So, include such constraint when setting up leaf partition result
relations for tuple-routing.

Committed.

0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patch

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels. If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Last posted here:
/messages/by-id/99fbfad6-b89b-9427-a6ca-197aad98c48e@lab.ntt.co.jp

Why does FormPartitionKeyDatum need this? Could that be documented in
a comment in here someplace, perhaps the header comment to
FormPartitionKeyDatum?

0003-Fix-RETURNING-to-work-correctly-after-tuple-routing.patch

In ExecInsert(), do not switch back to the root partitioned table
ResultRelInfo until after we finish ExecProcessReturning(), so that
RETURNING projection is done using the partition's descriptor. For
the projection to work correctly, we must initialize the same for
each leaf partition during ModifyTableState initialization.

Committed.

0004-Fix-some-issues-with-views-and-partitioned-tables.patch

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

The changes to execMain.c contain a hunk which has essentially the
same code twice. That looks like a mistake. Also, the patch doesn't
compile because convert_tuples_by_name() takes 3 arguments, not 4.

0005-Fix-some-wrong-thinking-in-check_new_partition_bound.patch

Because a given range bound in the PartitionBoundInfo.datums array
is sometimes a range lower bound and upper bound at other times, we
must be careful when assuming which, especially when interpreting
the result of partition_bound_bsearch which returns the index of the
greatest bound that is less than or equal to probe. Due to an error
in thinking about the same, the relevant code in
check_new_partition_bound() caused invalid partition (index = -1)
to be chosen as the partition being overlapped.

Last posted here:
/messages/by-id/603acb8b-5dec-31e8-29b0-609a68aac50f@lab.ntt.co.jp

                     }
+                    /*
+                     * If equal has been set to true or if there is no "gap"
+                     * between the bound at off1 and that at off1 + 1, the new
+                     * partition will overlap some partition.  In the former
+                     * case, the new lower bound is found to be equal to the
+                     * bound at off1, which could only ever be true if the
+                     * latter is the lower bound of some partition.  It's
+                     * clear in such a case that the new partition overlaps
+                     * that partition, whose index we get using its upper
+                     * bound (that is, using the bound at off1 + 1).
+                     */
                     else

Stylistically, we usually avoid this, or at least I do. The comment
should go inside the "else" block. But it looks OK apart from that,
so committed with a little rephrasing and reformatting of the comment.

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patch

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful. Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

+ Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);

I think you mean Assert(consider_typeid || indesc->tdhasoid ==
outdesc->tdhasoid);

But I wonder why we don't instead just change this function to
consider tdhasoid rather than tdtypeid. I mean, if the only point of
comparing the type OIDs is to find out whether the table-has-OIDs
setting matches, we could instead test that directly and avoid needing
to pass an extra argument. I wonder if there's some other reason this
code is there which is not documented in the comment...

0007-Avoid-code-duplication-in-map_partition_varattnos.patch

Code to map attribute numbers in map_partition_varattnos() duplicates
what convert_tuples_by_name_map() does. Avoid that as pointed out by
Álvaro Herrera.

Last posted here:
/messages/by-id/9ce97382-54c8-deb3-9ee9-a2ec271d866b@lab.ntt.co.jp

Committed.

0008-Avoid-DROP-TABLE-.-CASCADE-in-more-partitioning-test.patch

This is the new one. There were quite a few commits recently to fix the
breakage in regression tests due to not using ORDER BY in queries on
system catalogs and using DROP TABLE ... CASCADE. There were still some
instances of the latter in create_table.sql and alter_table.sql.

Committed.

Phew. Thanks for all the patches, sorry I'm having trouble keeping up.

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

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

#270Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#269)
3 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/20 4:18, Robert Haas wrote:

On Thu, Jan 19, 2017 at 12:15 AM, Amit Langote wrote:

0002-Set-ecxt_scantuple-correctly-for-tuple-routing.patch

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels. If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Last posted here:
/messages/by-id/99fbfad6-b89b-9427-a6ca-197aad98c48e@lab.ntt.co.jp

Why does FormPartitionKeyDatum need this? Could that be documented in
a comment in here someplace, perhaps the header comment to
FormPartitionKeyDatum?

FormPartitionKeyDatum() computes partition key expressions (if any) and
the expression evaluation machinery expects ecxt_scantuple to point to the
tuple to extract attribute values from.

FormPartitionKeyDatum() already has a tiny comment, which it seems is the
only thing we could say here about this there:

* the ecxt_scantuple slot of estate's per-tuple expr context must point to
* the heap tuple passed in.

In get_partition_for_tuple() which calls FormPartitionKeyDatum(), the
patch adds this comment (changed it a little from the last version):

+ /*
+  * Extract partition key from tuple. Expression evaluation machinery
+  * that FormPartitionKeyDatum() invokes expects ecxt_scantuple to
+  * point to the correct tuple slot.  The slot might have changed from
+  * what was used for the parent table if the table of the current
+  * partitioning level has different tuple descriptor from the parent.
+  * So update ecxt_scantuple accordingly.
+  */
+ ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);

It says why we need to change which slot ecxt_scantuple points to.

0004-Fix-some-issues-with-views-and-partitioned-tables.patch

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

The changes to execMain.c contain a hunk which has essentially the
same code twice. That looks like a mistake. Also, the patch doesn't
compile because convert_tuples_by_name() takes 3 arguments, not 4.

Actually, I realized that and sent the updated version [1]/messages/by-id/92fe2a71-5eb7-ee8d-53ef-cfd5a65dfc3d@lab.ntt.co.jp of this patch
that fixed this issue. In the updated version, I removed that code block
(the 2 copies of it), because we are still discussing what to do about
showing tuples in constraint violation (in this case, WITH CHECK OPTION
violation) messages. Anyway, attached here again.

0006-Avoid-tuple-coversion-in-common-partitioning-cases.patch

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful. Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

+ Assert(!consider_typeid && indesc->tdhasoid == outdesc->tdhasoid);

I think you mean Assert(consider_typeid || indesc->tdhasoid ==
outdesc->tdhasoid);

Ah, you're right.

But I wonder why we don't instead just change this function to
consider tdhasoid rather than tdtypeid. I mean, if the only point of
comparing the type OIDs is to find out whether the table-has-OIDs
setting matches, we could instead test that directly and avoid needing
to pass an extra argument. I wonder if there's some other reason this
code is there which is not documented in the comment...

With the following patch, regression tests run fine:

  if (indesc->natts == outdesc->natts &&
-     indesc->tdtypeid == outdesc->tdtypeid)
+     indesc->tdhasoid != outdesc->tdhasoid)
     {

If checking tdtypeid (instead of tdhasoid directly) has some other
consideration, I'd would have seen at least some tests broken by this
change. So, if we are to go with this, I too prefer it over my previous
proposal to add an argument to convert_tuples_by_name(). Attached 0003
implements the above approach.

Phew. Thanks for all the patches, sorry I'm having trouble keeping up.

Thanks a lot for taking time to look through them and committing.

Thanks,
Amit

[1]: /messages/by-id/92fe2a71-5eb7-ee8d-53ef-cfd5a65dfc3d@lab.ntt.co.jp
/messages/by-id/92fe2a71-5eb7-ee8d-53ef-cfd5a65dfc3d@lab.ntt.co.jp

Attachments:

0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchtext/x-diff; name=0001-Set-ecxt_scantuple-correctly-for-tuple-routing.patchDownload
From c7088290221a9fe0818139145b7bdf6731bc419a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 28 Dec 2016 10:10:26 +0900
Subject: [PATCH 1/3] Set ecxt_scantuple correctly for tuple-routing

In 2ac3ef7a01df859c62d0a02333b646d65eaec5ff, we changed things so that
it's possible for a different TupleTableSlot to be used for partitioned
tables at successively lower levels.  If we do end up changing the slot
from the original, we must update ecxt_scantuple to point to the new one
for partition key of the tuple to be computed correctly.

Also update the regression tests so that the code manipulating
ecxt_scantuple is covered.

Reported by: Rajkumar Raghuwanshi
Patch by: Amit Langote
Reports: https://www.postgresql.org/message-id/CAKcux6%3Dm1qyqB2k6cjniuMMrYXb75O-MB4qGQMu8zg-iGGLjDw%40mail.gmail.com
---
 src/backend/catalog/partition.c      | 30 +++++++++++++++++++++++-------
 src/backend/executor/execMain.c      |  2 --
 src/test/regress/expected/insert.out |  2 +-
 src/test/regress/sql/insert.sql      |  2 +-
 4 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3f8a950f37..9b2a71d0d5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1661,7 +1661,10 @@ get_partition_for_tuple(PartitionDispatch *pd,
 	bool		isnull[PARTITION_MAX_KEYS];
 	int			cur_offset,
 				cur_index;
-	int			i;
+	int			i,
+				result;
+	ExprContext *ecxt = GetPerTupleExprContext(estate);
+	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
 	/* start with the root partitioned table */
 	parent = pd[0];
@@ -1690,7 +1693,15 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			slot = myslot;
 		}
 
-		/* Extract partition key from tuple */
+		/*
+		 * Extract partition key from tuple. Expression evaluation machinery
+		 * that FormPartitionKeyDatum() invokes expects ecxt_scantuple to
+		 * point to the correct tuple slot.  The slot might have changed from
+		 * what was used for the parent table if the table of the current
+		 * partitioning level has different tuple descriptor from the parent.
+		 * So update ecxt_scantuple accordingly.
+		 */
+		ecxt->ecxt_scantuple = slot;
 		FormPartitionKeyDatum(parent, slot, estate, values, isnull);
 
 		if (key->strategy == PARTITION_STRATEGY_RANGE)
@@ -1745,16 +1756,21 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
+			result = -1;
 			*failed_at = RelationGetRelid(parent->reldesc);
-			return -1;
+			break;
 		}
-		else if (parent->indexes[cur_index] < 0)
-			parent = pd[-parent->indexes[cur_index]];
-		else
+		else if (parent->indexes[cur_index] >= 0)
+		{
+			result = parent->indexes[cur_index];
 			break;
+		}
+		else
+			parent = pd[-parent->indexes[cur_index]];
 	}
 
-	return parent->indexes[cur_index];
+	ecxt->ecxt_scantuple = ecxt_scantuple_old;
+	return result;
 }
 
 /*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index e6edcc06c2..8cb9691056 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3191,9 +3191,7 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
 {
 	int		result;
 	Oid		failed_at;
-	ExprContext *econtext = GetPerTupleExprContext(estate);
 
-	econtext->ecxt_scantuple = slot;
 	result = get_partition_for_tuple(pd, slot, estate, &failed_at);
 	if (result < 0)
 	{
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 538fe5b181..81af3ef497 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -320,7 +320,7 @@ drop table part_ee_ff, part_gg2_2, part_gg2_1, part_gg2, part_gg1, part_gg;
 drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3d5138a8e0..454e1ce2e7 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -193,7 +193,7 @@ drop table part_aa_bb, part_cc_dd, part_null, list_parted;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
-create table p1 (b int, a int not null) partition by range (b);
+create table p1 (b int not null, a int not null) partition by range ((b+0));
 create table p11 (like p1);
 alter table p11 drop a;
 alter table p11 add a int;
-- 
2.11.0

0002-Fix-some-issues-with-views-and-partitioned-tables.patchtext/x-diff; name=0002-Fix-some-issues-with-views-and-partitioned-tables.patchDownload
From 690a4aae05af5e015a9edfe501e957083205a86b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 6 Jan 2017 15:33:02 +0900
Subject: [PATCH 2/3] Fix some issues with views and partitioned tables

Automatically updatable views failed to handle partitioned tables.
Once that's fixed, WITH CHECK OPTIONS wouldn't work correctly without
the WCO expressions having been suitably converted for each partition
(think applying map_partition_varattnos to Vars in the WCO expressions
just like with partition constraint expressions).

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/executor/nodeModifyTable.c        | 40 +++++++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c          |  3 +-
 src/test/regress/expected/updatable_views.out | 24 ++++++++++++++++
 src/test/regress/sql/updatable_views.sql      | 19 +++++++++++++
 4 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2ac7407318..aa17aa0eb3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1778,6 +1778,46 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * Build WITH CHECK OPTION constraints for each leaf partition rel.
+	 * Note that we didn't build the withCheckOptionList for each partition
+	 * within the planner, but simple translation of the varattnos for each
+	 * partition will suffice.  This only occurs for the INSERT case;
+	 * UPDATE/DELETE cases are handled above.
+	 */
+	if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0)
+	{
+		List		*wcoList;
+
+		Assert(operation == CMD_INSERT);
+		resultRelInfo = mtstate->mt_partitions;
+		wcoList = linitial(node->withCheckOptionLists);
+		for (i = 0; i < mtstate->mt_num_partitions; i++)
+		{
+			Relation	partrel = resultRelInfo->ri_RelationDesc;
+			List	   *mapped_wcoList;
+			List	   *wcoExprs = NIL;
+			ListCell   *ll;
+
+			/* varno = node->nominalRelation */
+			mapped_wcoList = map_partition_varattnos(wcoList,
+													 node->nominalRelation,
+													 partrel, rel);
+			foreach(ll, mapped_wcoList)
+			{
+				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+				ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+											   mtstate->mt_plans[i]);
+
+				wcoExprs = lappend(wcoExprs, wcoExpr);
+			}
+
+			resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
+			resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+			resultRelInfo++;
+		}
+	}
+
+	/*
 	 * Initialize RETURNING projections if needed.
 	 */
 	if (node->returningLists)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d1ff3b20b6..d3e44fb135 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2249,7 +2249,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	if (base_rte->rtekind != RTE_RELATION ||
 		(base_rte->relkind != RELKIND_RELATION &&
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
-		 base_rte->relkind != RELKIND_VIEW))
+		 base_rte->relkind != RELKIND_VIEW &&
+		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
 		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
 
 	if (base_rte->tablesample)
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 2da3c069e1..2ae3613cec 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2367,3 +2367,27 @@ ERROR:  new row violates check option for view "v1"
 DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+ tableoid | a | b 
+----------+---+---
+ p11      | 1 | 2
+(1 row)
+
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+ERROR:  new row violates check option for view "pv_wco"
+DETAIL:  Failing row contains (2, 1).
+drop view pv, pv_wco;
+drop table p, p1, p11;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index ffc64d2de9..3c19edc8f7 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1112,3 +1112,22 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
 
 DROP VIEW v1;
 DROP TABLE t1;
+
+-- check that an auto-updatable view on a partitioned table works correctly
+create table p (a int, b int) partition by range (a, b);
+create table p1 (b int not null, a int not null) partition by range (b);
+create table p11 (like p1);
+alter table p11 drop a;
+alter table p11 add a int;
+alter table p11 drop a;
+alter table p11 add a int not null;
+alter table p1 attach partition p11 for values from (2) to (5);
+alter table p attach partition p1 for values from (1, 2) to (1, 10);
+
+create view pv as select * from p;
+insert into pv values (1, 2);
+select tableoid::regclass, * from p;
+create view pv_wco as select * from p where a = 0 with check option;
+insert into pv_wco values (1, 2);
+drop view pv, pv_wco;
+drop table p, p1, p11;
-- 
2.11.0

0003-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0003-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From 5ac4d27ebf34a29334dc5822e179559098c534f4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH 3/3] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 9 +++++----
 src/backend/catalog/partition.c        | 3 +--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b17ceafa6e..7c5427e987 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -214,12 +214,13 @@ convert_tuples_by_name(TupleDesc indesc,
 	attrMap = convert_tuples_by_name_map(indesc, outdesc, msg);
 
 	/*
-	 * Check to see if the map is one-to-one and the tuple types are the same.
-	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * Check to see if the map is one-to-one, in which case we need not do
+	 * tuple conversion.  That's not enough though if one of either source or
+	 * destination (tuples) contains OIDs; we'd need conversion in that case
+	 * to inject the right OID into the tuple datum.
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		indesc->tdhasoid != outdesc->tdhasoid)
 	{
 		same = true;
 		for (i = 0; i < n; i++)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9b2a71d0d5..f1d888a336 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1682,12 +1682,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
-- 
2.11.0

#271Keith Fiske
keith@omniti.com
In reply to: Amit Langote (#270)
Re: Declarative partitioning - another take

So testing things out in pg_partman for native sub-partitioning and ran
into what is a bug for me that I know I have to fix, but I'm curious if
this can be prevented in the first place within the native partitioning
code itself. The below shows a sub-partitioning set where the sub-partition
has a constraint range that is outside of the range of its parent. If the
columns were different I could see where this would be allowed, but the
same column is used throughout the levels of sub-partitioning.
Understandable if that may be too complex to check for, but figured I'd
bring it up as something I accidentally ran into in case you see an easy
way to prevent it.

This was encountered as of commit ba61a04bc7fefeee03416d9911eb825c4897c223.

keith@keith=# \d+ partman_test.time_taptest_table
Table "partman_test.time_taptest_table"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
col1 | integer | | | |
plain | |
col2 | text | | | |
extended | |
col3 | timestamp with time zone | | not null | now() |
plain | |
Partition key: RANGE (col3)
Partitions: partman_test.time_taptest_table_p2015 FOR VALUES FROM
('2015-01-01 00:00:00-05') TO ('2016-01-01 00:00:00-05'),
partman_test.time_taptest_table_p2016 FOR VALUES FROM
('2016-01-01 00:00:00-05') TO ('2017-01-01 00:00:00-05'),
partman_test.time_taptest_table_p2017 FOR VALUES FROM
('2017-01-01 00:00:00-05') TO ('2018-01-01 00:00:00-05'),
partman_test.time_taptest_table_p2018 FOR VALUES FROM
('2018-01-01 00:00:00-05') TO ('2019-01-01 00:00:00-05'),
partman_test.time_taptest_table_p2019 FOR VALUES FROM
('2019-01-01 00:00:00-05') TO ('2020-01-01 00:00:00-05')

keith@keith=# \d+ partman_test.time_taptest_table_p2015
Table "partman_test.time_taptest_table_p2015"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
col1 | integer | | | |
plain | |
col2 | text | | | |
extended | |
col3 | timestamp with time zone | | not null | now() |
plain | |
Partition of: partman_test.time_taptest_table FOR VALUES FROM ('2015-01-01
00:00:00-05') TO ('2016-01-01 00:00:00-05')
Partition key: RANGE (col3)
Partitions: partman_test.time_taptest_table_p2015_p2016_11 FOR VALUES FROM
('2016-11-01 00:00:00-04') TO ('2016-12-01 00:00:00-05'),
partman_test.time_taptest_table_p2015_p2016_12 FOR VALUES FROM
('2016-12-01 00:00:00-05') TO ('2017-01-01 00:00:00-05'),
partman_test.time_taptest_table_p2015_p2017_01 FOR VALUES FROM
('2017-01-01 00:00:00-05') TO ('2017-02-01 00:00:00-05'),
partman_test.time_taptest_table_p2015_p2017_02 FOR VALUES FROM
('2017-02-01 00:00:00-05') TO ('2017-03-01 00:00:00-05'),
partman_test.time_taptest_table_p2015_p2017_03 FOR VALUES FROM
('2017-03-01 00:00:00-05') TO ('2017-04-01 00:00:00-04')

keith@keith=# \d+ partman_test.time_taptest_table_p2015_p2017_03
Table
"partman_test.time_taptest_table_p2015_p2017_03"
Column | Type | Collation | Nullable | Default |
Storage | Stats target | Description
--------+--------------------------+-----------+----------+---------+----------+--------------+-------------
col1 | integer | | | |
plain | |
col2 | text | | | |
extended | |
col3 | timestamp with time zone | | not null | now() |
plain | |
Partition of: partman_test.time_taptest_table_p2015 FOR VALUES FROM
('2017-03-01 00:00:00-05') TO ('2017-04-01 00:00:00-04')

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

#272Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#269)
Re: Declarative partitioning - another take

On 2017-01-19 14:18:23 -0500, Robert Haas wrote:

Committed.

One of the patches around this topic committed recently seems to cause
valgrind failures like
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&amp;dt=2017-01-19%2008%3A40%3A02
:
==24969== Conditional jump or move depends on uninitialised value(s)
==24969== at 0x4C38DA: btint4cmp (nbtcompare.c:97)
==24969== by 0x83860C: FunctionCall2Coll (fmgr.c:1318)
==24969== by 0x536643: partition_bounds_equal (partition.c:627)
==24969== by 0x820864: equalPartitionDescs (relcache.c:1203)
==24969== by 0x82423A: RelationClearRelation (relcache.c:2553)
==24969== by 0x8248BA: RelationFlushRelation (relcache.c:2662)
==24969== by 0x824983: RelationCacheInvalidateEntry (relcache.c:2714)
==24969== by 0x81D9D6: LocalExecuteInvalidationMessage (inval.c:568)
==24969== by 0x81CB0D: ProcessInvalidationMessages (inval.c:444)
==24969== by 0x81D3CB: CommandEndInvalidationMessages (inval.c:1056)
==24969== by 0x4F6735: AtCCI_LocalCache (xact.c:1374)
==24969== by 0x4F8249: CommandCounterIncrement (xact.c:957)
==24969== Uninitialised value was created by a heap allocation
==24969== at 0x85AA83: palloc (mcxt.c:914)
==24969== by 0x53648E: RelationBuildPartitionDesc (partition.c:528)
==24969== by 0x823F93: RelationBuildDesc (relcache.c:1348)
==24969== by 0x8241DB: RelationClearRelation (relcache.c:2524)
==24969== by 0x8248BA: RelationFlushRelation (relcache.c:2662)
==24969== by 0x824983: RelationCacheInvalidateEntry (relcache.c:2714)
==24969== by 0x81D9D6: LocalExecuteInvalidationMessage (inval.c:568)
==24969== by 0x81CB0D: ProcessInvalidationMessages (inval.c:444)
==24969== by 0x81D3CB: CommandEndInvalidationMessages (inval.c:1056)
==24969== by 0x4F6735: AtCCI_LocalCache (xact.c:1374)
==24969== by 0x4F8249: CommandCounterIncrement (xact.c:957)
==24969== by 0x82538B: RelationSetNewRelfilenode (relcache.c:3490)
==24969==
==24969== VALGRINDERROR-END

Regards,

Andres

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

#273Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Andres Freund (#272)
Re: Declarative partitioning - another take

Hi Andres,

On 2017/01/20 15:15, Andres Freund wrote:

On 2017-01-19 14:18:23 -0500, Robert Haas wrote:

Committed.

One of the patches around this topic committed recently seems to cause
valgrind failures like
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&amp;dt=2017-01-19%2008%3A40%3A02
:
==24969== Conditional jump or move depends on uninitialised value(s)
==24969== at 0x4C38DA: btint4cmp (nbtcompare.c:97)
==24969== by 0x83860C: FunctionCall2Coll (fmgr.c:1318)
==24969== by 0x536643: partition_bounds_equal (partition.c:627)
==24969== by 0x820864: equalPartitionDescs (relcache.c:1203)
==24969== by 0x82423A: RelationClearRelation (relcache.c:2553)
==24969== by 0x8248BA: RelationFlushRelation (relcache.c:2662)
==24969== by 0x824983: RelationCacheInvalidateEntry (relcache.c:2714)
==24969== by 0x81D9D6: LocalExecuteInvalidationMessage (inval.c:568)
==24969== by 0x81CB0D: ProcessInvalidationMessages (inval.c:444)
==24969== by 0x81D3CB: CommandEndInvalidationMessages (inval.c:1056)
==24969== by 0x4F6735: AtCCI_LocalCache (xact.c:1374)
==24969== by 0x4F8249: CommandCounterIncrement (xact.c:957)
==24969== Uninitialised value was created by a heap allocation
==24969== at 0x85AA83: palloc (mcxt.c:914)
==24969== by 0x53648E: RelationBuildPartitionDesc (partition.c:528)
==24969== by 0x823F93: RelationBuildDesc (relcache.c:1348)
==24969== by 0x8241DB: RelationClearRelation (relcache.c:2524)
==24969== by 0x8248BA: RelationFlushRelation (relcache.c:2662)
==24969== by 0x824983: RelationCacheInvalidateEntry (relcache.c:2714)
==24969== by 0x81D9D6: LocalExecuteInvalidationMessage (inval.c:568)
==24969== by 0x81CB0D: ProcessInvalidationMessages (inval.c:444)
==24969== by 0x81D3CB: CommandEndInvalidationMessages (inval.c:1056)
==24969== by 0x4F6735: AtCCI_LocalCache (xact.c:1374)
==24969== by 0x4F8249: CommandCounterIncrement (xact.c:957)
==24969== by 0x82538B: RelationSetNewRelfilenode (relcache.c:3490)
==24969==
==24969== VALGRINDERROR-END

Thanks for the report. This being my first time reading a valgrind report
on buildfarm, is it correct to to assume that the command immediately
preceding VALGRINDERROR-BEGIN is what triggered the failure?

... LOG: statement: truncate list_parted;
==24969== VALGRINDERROR-BEGIN
==24969== Conditional jump or move depends on uninitialised value(s)
==24969== at 0x4C38DA: btint4cmp (nbtcompare.c:97)

So in this case: truncate list_parted?

Thanks,
Amit

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

#274Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#272)
Re: Declarative partitioning - another take

On Fri, Jan 20, 2017 at 1:15 AM, Andres Freund <andres@anarazel.de> wrote:

On 2017-01-19 14:18:23 -0500, Robert Haas wrote:

Committed.

One of the patches around this topic committed recently seems to cause
valgrind failures like
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&amp;dt=2017-01-19%2008%3A40%3A02
:

Tom Lane independently reported this same issue. I believe I've fixed
it. Sorry for not noticing and crediting this report also.

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

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

#275Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#274)
Re: Declarative partitioning - another take

On 2017/01/21 6:29, Robert Haas wrote:

On Fri, Jan 20, 2017 at 1:15 AM, Andres Freund <andres@anarazel.de> wrote:

On 2017-01-19 14:18:23 -0500, Robert Haas wrote:

Committed.

One of the patches around this topic committed recently seems to cause
valgrind failures like
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&amp;dt=2017-01-19%2008%3A40%3A02
:

Tom Lane independently reported this same issue. I believe I've fixed
it. Sorry for not noticing and crediting this report also.

Thanks Robert for looking at this and committing the fix.

Regards,
Amit

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

#276Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#270)
Re: Declarative partitioning - another take

On Thu, Jan 19, 2017 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

But I wonder why we don't instead just change this function to
consider tdhasoid rather than tdtypeid. I mean, if the only point of
comparing the type OIDs is to find out whether the table-has-OIDs
setting matches, we could instead test that directly and avoid needing
to pass an extra argument. I wonder if there's some other reason this
code is there which is not documented in the comment...

With the following patch, regression tests run fine:

if (indesc->natts == outdesc->natts &&
-     indesc->tdtypeid == outdesc->tdtypeid)
+     indesc->tdhasoid != outdesc->tdhasoid)
{

If checking tdtypeid (instead of tdhasoid directly) has some other
consideration, I'd would have seen at least some tests broken by this
change. So, if we are to go with this, I too prefer it over my previous
proposal to add an argument to convert_tuples_by_name(). Attached 0003
implements the above approach.

I think this is not quite right. First, the patch compares the
tdhasoid status with != rather than ==, which would have the effect of
saying that we can skip conversion of the has-OID statuses do NOT
match. That can't be right. Second, I believe that the comments
imply that conversion should be done if *either* tuple has OIDs. I
believe that's because whoever wrote this comment thought that we
needed to replace the OID if the tuple already had one, which is what
do_convert_tuple would do. I'm not sure whether that's really
necessary, but we're less likely to break anything if we preserve the
existing behavior, and I don't think we lose much from doing so
because few user tables will have OIDs. So I would change this test
to if (indesc->natts == outdesc->natts && !indesc->tdhasoid &&
!outdesc->tdhasoid), and I'd revise the one in
convert_tuples_by_position() to match. Then I think it's much clearer
that we're just optimizing what's there already, not changing the
behavior.

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

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

#277Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#270)
Re: Declarative partitioning - another take

On Thu, Jan 19, 2017 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Committed 0001 and 0002. See my earlier email for comments on 0003.

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

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

#278Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Keith Fiske (#271)
Re: Declarative partitioning - another take

Hi Keith,

On 2017/01/20 12:40, Keith Fiske wrote:

So testing things out in pg_partman for native sub-partitioning and ran
into what is a bug for me that I know I have to fix, but I'm curious if
this can be prevented in the first place within the native partitioning
code itself. The below shows a sub-partitioning set where the sub-partition
has a constraint range that is outside of the range of its parent. If the
columns were different I could see where this would be allowed, but the
same column is used throughout the levels of sub-partitioning.
Understandable if that may be too complex to check for, but figured I'd
bring it up as something I accidentally ran into in case you see an easy
way to prevent it.

This was discussed. See Robert's response (2nd part of):
/messages/by-id/CA+TgmoaQABrsLQK4ms_4NiyavyJGS-b6ZFkZBBNC+-P5DjJNFA@mail.gmail.com

In short, defining partitions across different levels such that the data
user intended to insert into the table (the part of the sub-partition's
range that doesn't overlap with its parent's) couldn't be, that's an
operator error. It's like adding contradictory check constraints to the
table:

create table foo (a int check (a > 0 and a < 0));
insert into foo values (1);
ERROR: new row for relation "foo" violates check constraint "foo_a_check"
DETAIL: Failing row contains (1).

One (perhaps the only) thing that could be done is to warn users to
prevent this kind of mistake through documentation. Trying to do anything
else in the core partitioning code is making it too complicated for not
much benefit (also see Robert's last line, :)).

Thanks,
Amit

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

#279Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#276)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/01/25 2:56, Robert Haas wrote:

On Thu, Jan 19, 2017 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

But I wonder why we don't instead just change this function to
consider tdhasoid rather than tdtypeid. I mean, if the only point of
comparing the type OIDs is to find out whether the table-has-OIDs
setting matches, we could instead test that directly and avoid needing
to pass an extra argument. I wonder if there's some other reason this
code is there which is not documented in the comment...

With the following patch, regression tests run fine:

if (indesc->natts == outdesc->natts &&
-     indesc->tdtypeid == outdesc->tdtypeid)
+     indesc->tdhasoid != outdesc->tdhasoid)
{

If checking tdtypeid (instead of tdhasoid directly) has some other
consideration, I'd would have seen at least some tests broken by this
change. So, if we are to go with this, I too prefer it over my previous
proposal to add an argument to convert_tuples_by_name(). Attached 0003
implements the above approach.

I think this is not quite right. First, the patch compares the
tdhasoid status with != rather than ==, which would have the effect of
saying that we can skip conversion of the has-OID statuses do NOT
match. That can't be right.

You're right.

Second, I believe that the comments
imply that conversion should be done if *either* tuple has OIDs. I
believe that's because whoever wrote this comment thought that we
needed to replace the OID if the tuple already had one, which is what
do_convert_tuple would do. I'm not sure whether that's really
necessary, but we're less likely to break anything if we preserve the
existing behavior, and I don't think we lose much from doing so
because few user tables will have OIDs. So I would change this test
to if (indesc->natts == outdesc->natts && !indesc->tdhasoid &&
!outdesc->tdhasoid), and I'd revise the one in
convert_tuples_by_position() to match. Then I think it's much clearer
that we're just optimizing what's there already, not changing the
behavior.

Agreed. Updated patch attached.

Thanks,
Amit

Attachments:

0001-Avoid-tuple-coversion-in-common-partitioning-cases.patchtext/x-diff; name=0001-Avoid-tuple-coversion-in-common-partitioning-cases.patchDownload
From c1fa4b9f04f328b8df54ef487ee9d46f5978e0de Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 26 Dec 2016 17:44:14 +0900
Subject: [PATCH] Avoid tuple coversion in common partitioning cases

Currently, the tuple conversion is performed after a tuple is routed,
even if the attributes of a target leaf partition map one-to-one with
those of the root table, which is wasteful.  Avoid that by making
convert_tuples_by_name() return a NULL map for such cases.

Reported by: n/a
Patch by: Amit Langote
Reports: n/a
---
 src/backend/access/common/tupconvert.c | 18 ++++++++++--------
 src/backend/catalog/partition.c        |  3 +--
 2 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/common/tupconvert.c b/src/backend/access/common/tupconvert.c
index b17ceafa6e..a4012525d8 100644
--- a/src/backend/access/common/tupconvert.c
+++ b/src/backend/access/common/tupconvert.c
@@ -138,12 +138,13 @@ convert_tuples_by_position(TupleDesc indesc,
 						   nincols, noutcols)));
 
 	/*
-	 * Check to see if the map is one-to-one and the tuple types are the same.
-	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * Check to see if the map is one-to-one, in which case we need not do
+	 * the tuple conversion.  That's not enough though if either source or
+	 * destination (tuples) contains OIDs; we'd need conversion in that case
+	 * to inject the right OID into the tuple datum.
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		!indesc->tdhasoid && !outdesc->tdhasoid)
 	{
 		for (i = 0; i < n; i++)
 		{
@@ -214,12 +215,13 @@ convert_tuples_by_name(TupleDesc indesc,
 	attrMap = convert_tuples_by_name_map(indesc, outdesc, msg);
 
 	/*
-	 * Check to see if the map is one-to-one and the tuple types are the same.
-	 * (We check the latter because if they're not, we want to do conversion
-	 * to inject the right OID into the tuple datum.)
+	 * Check to see if the map is one-to-one, in which case we need not do
+	 * the tuple conversion.  That's not enough though if either source or
+	 * destination (tuples) contains OIDs; we'd need conversion in that case
+	 * to inject the right OID into the tuple datum.
 	 */
 	if (indesc->natts == outdesc->natts &&
-		indesc->tdtypeid == outdesc->tdtypeid)
+		!indesc->tdhasoid && !outdesc->tdhasoid)
 	{
 		same = true;
 		for (i = 0; i < n; i++)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7be60529c5..4bcef58763 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1700,12 +1700,11 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			return -1;
 		}
 
-		if (myslot != NULL)
+		if (myslot != NULL && map != NULL)
 		{
 			HeapTuple	tuple = ExecFetchSlotTuple(slot);
 
 			ExecClearTuple(myslot);
-			Assert(map != NULL);
 			tuple = do_convert_tuple(tuple, map);
 			ExecStoreTuple(tuple, myslot, InvalidBuffer, true);
 			slot = myslot;
-- 
2.11.0

#280Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#277)
Re: Declarative partitioning - another take

On 2017/01/25 5:55, Robert Haas wrote:

On Thu, Jan 19, 2017 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Committed 0001 and 0002. See my earlier email for comments on 0003.

It seems patches for all the issues mentioned in this thread so far,
except 0003 (I just sent an updated version in another email), have been
committed. Thanks a lot for your time. I will create new threads for any
more patches from here on.

Regards,
Amit

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

#281Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#280)
Re: Declarative partitioning - another take

The documentation available at
https://www.postgresql.org/docs/devel/static/sql-createtable.html,
does not make it clear that the lower bound of a range partition is
always inclusive and the higher one is exclusive. I think a note in
section " PARTITION OF parent_table FOR VALUES partition_bound_spec"
would be helpful.

On Wed, Jan 25, 2017 at 7:11 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/01/25 5:55, Robert Haas wrote:

On Thu, Jan 19, 2017 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

[ new patches ]

Committed 0001 and 0002. See my earlier email for comments on 0003.

It seems patches for all the issues mentioned in this thread so far,
except 0003 (I just sent an updated version in another email), have been
committed. Thanks a lot for your time. I will create new threads for any
more patches from here on.

Regards,
Amit

--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company

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

#282Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#281)
Re: Declarative partitioning - another take

Hi Ashutosh,

On 2017/01/25 14:54, Ashutosh Bapat wrote:

The documentation available at
https://www.postgresql.org/docs/devel/static/sql-createtable.html,
does not make it clear that the lower bound of a range partition is
always inclusive and the higher one is exclusive. I think a note in
section " PARTITION OF parent_table FOR VALUES partition_bound_spec"
would be helpful.

Yes, I'm working on a documentation patch for that and a few other things
such as revising the Partitioning chapter.

Thanks,
Amit

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

#283Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Ashutosh Bapat (#281)
Re: Declarative partitioning - another take

On 1/25/17 12:54 AM, Ashutosh Bapat wrote:

The documentation available at
https://www.postgresql.org/docs/devel/static/sql-createtable.html,
does not make it clear that the lower bound of a range partition is
always inclusive and the higher one is exclusive. I think a note in
section " PARTITION OF parent_table FOR VALUES partition_bound_spec"
would be helpful.

Hmm. I see the practical use of that, but I think this is going to be a
source of endless confusion. Can we make that a bit clearer in the
syntax, for example by using additional keywords (INCLUSIVE/EXCLUSIVE)?

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

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

#284Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Peter Eisentraut (#283)
Re: Declarative partitioning - another take

On 2017/01/31 6:42, Peter Eisentraut wrote:

On 1/25/17 12:54 AM, Ashutosh Bapat wrote:

The documentation available at
https://www.postgresql.org/docs/devel/static/sql-createtable.html,
does not make it clear that the lower bound of a range partition is
always inclusive and the higher one is exclusive. I think a note in
section " PARTITION OF parent_table FOR VALUES partition_bound_spec"
would be helpful.

Hmm. I see the practical use of that, but I think this is going to be a
source of endless confusion. Can we make that a bit clearer in the
syntax, for example by using additional keywords (INCLUSIVE/EXCLUSIVE)?

The decision not to make that configurable with INCLUSIVE/EXCLUSIVE syntax
was deliberate. To summarize, we can start with a default configuration
catering to most practical cases (that is, inclusive lower and exclusive
upper bounds) and documenting so (not done yet, which I will post a doc
patch today for). If it turns out that there is some demand for making
that configurable, we can later add the code to handle that internally
plus the syntax. But *starting* with that syntax means we have to
potentially needlessly carry the code to handle seldom used cases that
could not be made as efficient as it is now with all lower bounds being
inclusive and upper bounds exclusive.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZou4ApEvC_nfhOxsi5G4SoD_evwNaiYn60ZcJ4XB_-QQ@mail.gmail.com
/messages/by-id/CA+TgmoZou4ApEvC_nfhOxsi5G4SoD_evwNaiYn60ZcJ4XB_-QQ@mail.gmail.com

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

#285Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#283)
Re: Declarative partitioning - another take

On Mon, Jan 30, 2017 at 4:42 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/25/17 12:54 AM, Ashutosh Bapat wrote:

The documentation available at
https://www.postgresql.org/docs/devel/static/sql-createtable.html,
does not make it clear that the lower bound of a range partition is
always inclusive and the higher one is exclusive. I think a note in
section " PARTITION OF parent_table FOR VALUES partition_bound_spec"
would be helpful.

Hmm. I see the practical use of that, but I think this is going to be a
source of endless confusion. Can we make that a bit clearer in the
syntax, for example by using additional keywords (INCLUSIVE/EXCLUSIVE)?

I am not in favor of adding mandatory noise words to the syntax; it's
fairly verbose already. I think it would be reasonable to eventually
consider supporting optional keywords to allow multiple behaviors, but
I don't think that the usefulness of that has been so firmly
established that we should do it right this minute. I think there are
a heck of a lot of other things about this partitioning implementation
that are more urgently in need of improvement.

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

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

#286amul sul
sulamul@gmail.com
In reply to: Robert Haas (#285)
Re: Declarative partitioning - another take

Hi Amit,

Regarding following code in ATExecDropNotNull function, I don't see
any special check for RANGE partitioned, is it intended to have same
restriction for LIST partitioned too, I guess not?

/*
* If the table is a range partitioned table, check that the column is not
* in the partition key.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionKey key = RelationGetPartitionKey(rel);
int partnatts = get_partition_natts(key),
i;

for (i = 0; i < partnatts; i++)
{
AttrNumber partattnum = get_partition_col_attnum(key, i);

if (partattnum == attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in range partition key",
colName)));
}
}

Regards,
Amul

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

#287Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: amul sul (#286)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/02/08 21:20, amul sul wrote:

Regarding following code in ATExecDropNotNull function, I don't see
any special check for RANGE partitioned, is it intended to have same
restriction for LIST partitioned too, I guess not?

/*
* If the table is a range partitioned table, check that the column is not
* in the partition key.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionKey key = RelationGetPartitionKey(rel);
int partnatts = get_partition_natts(key),
i;

for (i = 0; i < partnatts; i++)
{
AttrNumber partattnum = get_partition_col_attnum(key, i);

if (partattnum == attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in range partition key",
colName)));
}
}

Good catch! Seems like an oversight, attached fixes it.

Thanks,
Amit

Attachments:

0001-Check-partition-strategy-in-ATExecDropNotNull.patchtext/x-diff; name=0001-Check-partition-strategy-in-ATExecDropNotNull.patchDownload
From b84ac63b6b4acd19a09e8507cf199db127c06d71 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 9 Feb 2017 10:31:58 +0900
Subject: [PATCH] Check partition strategy in ATExecDropNotNull

We should prevent dropping the NOT NULL constraint on a column only
if the column is in the *range* partition key (as the error message
also says).  Add a regression test while at it.

Reported by: Amul Sul
Patch by: Amit Langote
Report: https://postgr.es/m/CAAJ_b95g5AgkpJKbLajAt8ovKub874-B9X08PiOqHvVfMO2mLw%40mail.gmail.com
---
 src/backend/commands/tablecmds.c          | 22 +++++++++++++---------
 src/test/regress/expected/alter_table.out |  3 +++
 src/test/regress/sql/alter_table.sql      |  3 +++
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 37a4c4a3d6..f33aa70da6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5593,18 +5593,22 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		PartitionKey key = RelationGetPartitionKey(rel);
-		int			partnatts = get_partition_natts(key),
-					i;
 
-		for (i = 0; i < partnatts; i++)
+		if (get_partition_strategy(key) == PARTITION_STRATEGY_RANGE)
 		{
-			AttrNumber	partattnum = get_partition_col_attnum(key, i);
+			int			partnatts = get_partition_natts(key),
+						i;
 
-			if (partattnum == attnum)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-						 errmsg("column \"%s\" is in range partition key",
-								colName)));
+			for (i = 0; i < partnatts; i++)
+			{
+				AttrNumber	partattnum = get_partition_col_attnum(key, i);
+
+				if (partattnum == attnum)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+							 errmsg("column \"%s\" is in range partition key",
+									colName)));
+			}
 		}
 	}
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d8e7b61294..e6d45fbf9c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3330,6 +3330,9 @@ ALTER TABLE list_parted2 DROP COLUMN b;
 ERROR:  cannot drop column named in partition key
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
+-- cannot drop NOT NULL constraint of a range partition key column
+ALTER TABLE range_parted ALTER a DROP NOT NULL;
+ERROR:  column "a" is in range partition key
 -- cleanup: avoid using CASCADE
 DROP TABLE list_parted, part_1;
 DROP TABLE list_parted2, part_2, part_5, part_5_a;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 1f551ec53c..12edb8b3ba 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2189,6 +2189,9 @@ ALTER TABLE part_2 INHERIT inh_test;
 ALTER TABLE list_parted2 DROP COLUMN b;
 ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
+-- cannot drop NOT NULL constraint of a range partition key column
+ALTER TABLE range_parted ALTER a DROP NOT NULL;
+
 -- cleanup: avoid using CASCADE
 DROP TABLE list_parted, part_1;
 DROP TABLE list_parted2, part_2, part_5, part_5_a;
-- 
2.11.0

#288amul sul
sulamul@gmail.com
In reply to: Amit Langote (#287)
Re: Declarative partitioning - another take

About 0001-Check-partition-strategy-in-ATExecDropNotNull.patch,
following test is already covered in alter_table.sql @ Line # 1918,
instead of this kindly add test for list_partition:

77 +-- cannot drop NOT NULL constraint of a range partition key column
78 +ALTER TABLE range_parted ALTER a DROP NOT NULL;
79 +

Regards,
Amul

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

#289Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: amul sul (#288)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/02/09 15:22, amul sul wrote:

About 0001-Check-partition-strategy-in-ATExecDropNotNull.patch,
following test is already covered in alter_table.sql @ Line # 1918,
instead of this kindly add test for list_partition:

77 +-- cannot drop NOT NULL constraint of a range partition key column
78 +ALTER TABLE range_parted ALTER a DROP NOT NULL;
79 +

Thanks for the review! You're right. Updated patch attached.

Thanks,
Amit

Attachments:

0001-Check-partition-strategy-in-ATExecDropNotNull.patchtext/x-diff; name=0001-Check-partition-strategy-in-ATExecDropNotNull.patchDownload
From a4335048e92462fb55fa6db0d830c7c066cd7011 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 10 Feb 2017 14:52:18 +0900
Subject: [PATCH] Check partition strategy in ATExecDropNotNull

We should prevent dropping the NOT NULL constraint on a column only
if the column is in the *range* partition key (as the error message
also says).  Add a regression test while at it.

Reported by: Amul Sul
Patch by: Amit Langote
Report: https://postgr.es/m/CAAJ_b95g5AgkpJKbLajAt8ovKub874-B9X08PiOqHvVfMO2mLw%40mail.gmail.com
---
 src/backend/commands/tablecmds.c          | 22 +++++++++++++---------
 src/test/regress/expected/alter_table.out |  4 ++++
 src/test/regress/sql/alter_table.sql      |  5 +++++
 3 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 37a4c4a3d6..f33aa70da6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5593,18 +5593,22 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		PartitionKey key = RelationGetPartitionKey(rel);
-		int			partnatts = get_partition_natts(key),
-					i;
 
-		for (i = 0; i < partnatts; i++)
+		if (get_partition_strategy(key) == PARTITION_STRATEGY_RANGE)
 		{
-			AttrNumber	partattnum = get_partition_col_attnum(key, i);
+			int			partnatts = get_partition_natts(key),
+						i;
 
-			if (partattnum == attnum)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-						 errmsg("column \"%s\" is in range partition key",
-								colName)));
+			for (i = 0; i < partnatts; i++)
+			{
+				AttrNumber	partattnum = get_partition_col_attnum(key, i);
+
+				if (partattnum == attnum)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+							 errmsg("column \"%s\" is in range partition key",
+									colName)));
+			}
 		}
 	}
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d8e7b61294..b0e80a7788 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3029,6 +3029,10 @@ ERROR:  cannot alter type of column referenced in partition key expression
 -- cannot drop NOT NULL on columns in the range partition key
 ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
 ERROR:  column "a" is in range partition key
+-- it's fine however to drop one on the list partition key column
+CREATE TABLE list_partitioned (a int not null) partition by list (a);
+ALTER TABLE list_partitioned ALTER a DROP NOT NULL;
+DROP TABLE list_partitioned;
 -- partitioned table cannot participate in regular inheritance
 CREATE TABLE foo (
 	a int,
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 1f551ec53c..7513769359 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1917,6 +1917,11 @@ ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
 -- cannot drop NOT NULL on columns in the range partition key
 ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
 
+-- it's fine however to drop one on the list partition key column
+CREATE TABLE list_partitioned (a int not null) partition by list (a);
+ALTER TABLE list_partitioned ALTER a DROP NOT NULL;
+DROP TABLE list_partitioned;
+
 -- partitioned table cannot participate in regular inheritance
 CREATE TABLE foo (
 	a int,
-- 
2.11.0

#290Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#289)
Re: Declarative partitioning - another take

On Fri, Feb 10, 2017 at 12:54 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/02/09 15:22, amul sul wrote:

About 0001-Check-partition-strategy-in-ATExecDropNotNull.patch,
following test is already covered in alter_table.sql @ Line # 1918,
instead of this kindly add test for list_partition:

77 +-- cannot drop NOT NULL constraint of a range partition key column
78 +ALTER TABLE range_parted ALTER a DROP NOT NULL;
79 +

Thanks for the review! You're right. Updated patch attached.

Committed, thanks.

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

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

#291Yugo Nagata
nagata@sraoss.co.jp
In reply to: Amit Langote (#238)
1 attachment(s)
Re: Declarative partitioning - another take

Hi,

I found that a comment for PartitionRoot in ResultRelInfo is missing.
Although this is trivial, since all other members have comments, I
think it is needed. Attached is the patch to fix it.

Regards,
Yugo Nagata

On Tue, 27 Dec 2016 17:59:05 +0900
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/12/26 19:46, Amit Langote wrote:

(Perhaps, the following should be its own new thread)

I noticed that ExecProcessReturning() doesn't work properly after tuple
routing (example shows how returning tableoid currently fails but I
mention some other issues below):

create table p (a int, b int) partition by range (a);
create table p1 partition of p for values from (1) to (10);
insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
- | 1 |
(1 row)

INSERT 0 1

I tried to fix that in 0007 to get:

insert into p values (1) returning tableoid::regclass, *;
tableoid | a | b
----------+---+---
p | 1 |
(1 row)

INSERT 0 1

But I think it *may* be wrong to return the root table OID for tuples
inserted into leaf partitions, because with select we get partition OIDs:

select tableoid::regclass, * from p;
tableoid | a | b
----------+---+---
p1 | 1 |
(1 row)

If so, that means we should build the projection info (corresponding to
the returning list) for each target partition somehow. ISTM, that's going
to have to be done within the planner by appropriate inheritance
translation of the original returning targetlist.

Turns out getting the 2nd result may not require planner tweaks after all.
Unless I'm missing something, translation of varattnos of the RETURNING
target list can be done as late as ExecInitModifyTable() for the insert
case, unlike update/delete (which do require planner's attention).

I updated the patch 0007 to implement the same, including the test. While
doing that, I realized map_partition_varattnos introduced in 0003 is
rather restrictive in its applicability, because it assumes varno = 1 for
the expressions it accepts as input for the mapping. Mapping returning
(target) list required modifying map_partition_varattnos to accept
target_varno as additional argument. That way, we can map arbitrary
expressions from the parent attributes numbers to partition attribute
numbers for expressions not limited to partition constraints.

Patches 0001 to 0006 unchanged.

Thanks,
Amit

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

execnodes_comment.patchtext/x-diff; name=execnodes_comment.patchDownload
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6332ea0..845bdf2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -326,6 +326,7 @@ typedef struct JunkFilter
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
  *		PartitionCheck			partition check expression
  *		PartitionCheckExpr		partition check expression state
+ *		PartitionRoot			relation descriptor for parent relation
  * ----------------
  */
 typedef struct ResultRelInfo
#292Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Yugo Nagata (#291)
Re: Declarative partitioning - another take

Nagata-san,

On 2017/02/23 16:17, Yugo Nagata wrote:

Hi,

I found that a comment for PartitionRoot in ResultRelInfo is missing.
Although this is trivial, since all other members have comments, I
think it is needed. Attached is the patch to fix it.

Thanks for taking care of that.

+ * PartitionRoot relation descriptor for parent relation

Maybe: relation descriptor for root parent relation

Thanks,
Amit

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

#293Yugo Nagata
nagata@sraoss.co.jp
In reply to: Amit Langote (#292)
1 attachment(s)
Re: Declarative partitioning - another take

Hi Amit,

On Thu, 23 Feb 2017 16:29:32 +0900
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Thanks for taking care of that.

+ * PartitionRoot relation descriptor for parent relation

Maybe: relation descriptor for root parent relation

This seems better. Patch is updated.

Thanks,
--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

execnodes_comment.patchtext/x-diff; name=execnodes_comment.patchDownload
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6332ea0..af3367a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -326,6 +326,7 @@ typedef struct JunkFilter
  *		onConflictSetWhere		list of ON CONFLICT DO UPDATE exprs (qual)
  *		PartitionCheck			partition check expression
  *		PartitionCheckExpr		partition check expression state
+ *		PartitionRoot			relation descriptor for root parent relation
  * ----------------
  */
 typedef struct ResultRelInfo
#294Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Etsuro Fujita (#209)
Re: Declarative partitioning - another take

On 2016/12/14 16:20, Etsuro Fujita wrote:

On 2016/12/09 19:46, Maksim Milyutin wrote:

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the
children inherit it;

The first one has been implemented in pg_pathman somehow, but the code
relies on dirty hacks, so the FDW API has to be improved. As for the
extended index support, it doesn't look like a super-hard task.

That would be great! I'd like to help review the first one.

There seems to be no work on the first one, so I'd like to work on that.

Best regards,
Etsuro Fujita

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

#295Maksim Milyutin
m.milyutin@postgrespro.ru
In reply to: Etsuro Fujita (#294)
Re: Declarative partitioning - another take

On 07.04.2017 13:05, Etsuro Fujita wrote:

On 2016/12/14 16:20, Etsuro Fujita wrote:

On 2016/12/09 19:46, Maksim Milyutin wrote:

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the
children inherit it;

The first one has been implemented in pg_pathman somehow, but the code
relies on dirty hacks, so the FDW API has to be improved. As for the
extended index support, it doesn't look like a super-hard task.

That would be great! I'd like to help review the first one.

There seems to be no work on the first one, so I'd like to work on that.

Yes, you can start to work on this, I'll join later as a reviewer.

--
Maksim Milyutin
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

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

#296Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Maksim Milyutin (#295)
Re: Declarative partitioning - another take

Hi,

I have observed below with the statement triggers.

I am able to create statement triggers at root partition, but these
triggers, not getting fired on updating partition.

CREATE TABLE pt (a INT, b INT) PARTITION BY RANGE(a);
CREATE TABLE pt1 PARTITION OF pt FOR VALUES FROM (1) to (7);
CREATE TABLE pt2 PARTITION OF pt FOR VALUES FROM (7) to (11);
INSERT INTO pt (a,b) SELECT i,i FROM generate_series(1,10)i;
CREATE TABLE pt_trigger(TG_NAME varchar,TG_TABLE_NAME varchar,TG_LEVEL
varchar,TG_WHEN varchar);
CREATE OR REPLACE FUNCTION process_pt_trigger() RETURNS TRIGGER AS $pttg$
BEGIN
IF (TG_OP = 'UPDATE') THEN
INSERT INTO pt_trigger SELECT
TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN;
RETURN NEW;
END IF;
RETURN NULL;
END;
$pttg$ LANGUAGE plpgsql;
CREATE TRIGGER pt_trigger_after_p0 AFTER UPDATE ON pt FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER pt_trigger_before_p0 BEFORE UPDATE ON pt FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();

postgres=# UPDATE pt SET a = 2 WHERE a = 1;
UPDATE 1
postgres=# SELECT * FROM pt_trigger ORDER BY 1;
tg_name | tg_table_name | tg_level | tg_when
---------+---------------+----------+---------
(0 rows)

no statement level trigger fired in this case, is this expected behaviour??

but if i am creating triggers on leaf partition, trigger is getting fired.

CREATE TRIGGER pt_trigger_after_p1 AFTER UPDATE ON pt1 FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER pt_trigger_before_p1 BEFORE UPDATE ON pt1 FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();

postgres=# UPDATE pt SET a = 5 WHERE a = 4;
UPDATE 1
postgres=# SELECT * FROM pt_trigger ORDER BY 1;
tg_name | tg_table_name | tg_level | tg_when
----------------------+---------------+-----------+---------
pt_trigger_after_p1 | pt1 | STATEMENT | AFTER
pt_trigger_before_p1 | pt1 | STATEMENT | BEFORE
(2 rows)

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#297Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#296)
2 attachment(s)
Re: Declarative partitioning - another take

Hi Rajkumar,

Thanks for testing and the report.

On 2017/04/21 17:00, Rajkumar Raghuwanshi wrote:

Hi,

I have observed below with the statement triggers.

I am able to create statement triggers at root partition, but these
triggers, not getting fired on updating partition.

CREATE TABLE pt (a INT, b INT) PARTITION BY RANGE(a);
CREATE TABLE pt1 PARTITION OF pt FOR VALUES FROM (1) to (7);
CREATE TABLE pt2 PARTITION OF pt FOR VALUES FROM (7) to (11);
INSERT INTO pt (a,b) SELECT i,i FROM generate_series(1,10)i;
CREATE TABLE pt_trigger(TG_NAME varchar,TG_TABLE_NAME varchar,TG_LEVEL
varchar,TG_WHEN varchar);
CREATE OR REPLACE FUNCTION process_pt_trigger() RETURNS TRIGGER AS $pttg$
BEGIN
IF (TG_OP = 'UPDATE') THEN
INSERT INTO pt_trigger SELECT
TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN;
RETURN NEW;
END IF;
RETURN NULL;
END;
$pttg$ LANGUAGE plpgsql;
CREATE TRIGGER pt_trigger_after_p0 AFTER UPDATE ON pt FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER pt_trigger_before_p0 BEFORE UPDATE ON pt FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();

postgres=# UPDATE pt SET a = 2 WHERE a = 1;
UPDATE 1
postgres=# SELECT * FROM pt_trigger ORDER BY 1;
tg_name | tg_table_name | tg_level | tg_when
---------+---------------+----------+---------
(0 rows)

no statement level trigger fired in this case, is this expected behaviour??

I think we discussed this during the development and decided to allow
per-statement triggers on partitioned tables [1]/messages/by-id/8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp, but it seems it doesn't
quite work for update/delete as your example shows. You can however see
that per-statement triggers of the root partitioned table *are* fired in
the case of insert.

The reason it doesn't work is that we do not allocate ResultRelInfos for
partitioned tables (not even for the root partitioned table in the
update/delete cases), because the current implementation assumes they are
not required. That's fine only so long as we consider that no rows are
inserted into them, no indexes need to be updated, and that no row-level
triggers are to be fired. But it misses the fact that we do allow
statement-level triggers on partitioned tables. So, we should fix things
such that ResultRelInfos are allocated so that any statement-level
triggers are fired. But there are following questions to consider:

1. Should we consider only the root partitioned table or all partitioned
tables in a given hierarchy, including the child partitioned tables?
Note that this is related to a current limitation of updating/deleting
inheritance hierarchies that we do not currently fire per-statement
triggers of the child tables. See the TODO item in wiki:
https://wiki.postgresql.org/wiki/Todo#Triggers, which says: "When
statement-level triggers are defined on a parent table, have them fire
only on the parent table, and fire child table triggers only where
appropriate"

2. Do we apply the same to inserts on the partitioned tables? Since
insert on a partitioned table implicitly affects its partitions, one
might say that we would need to fire per-statement insert triggers of
all the partitions.

Meanwhile, attached is a set of patches to fix this. Descriptions follow:

0001: fire per-statement triggers of the partitioned table mentioned in a
given update or delete statement

0002: fire per-statement triggers of the child tables during update/delete
of inheritance hierarchies (including the partitioned table case)

Depending on the answer to 2 above, we can arrange for the per-statement
triggers of all the partitions to be fired upon insert into the
partitioned table.

but if i am creating triggers on leaf partition, trigger is getting fired.

CREATE TRIGGER pt_trigger_after_p1 AFTER UPDATE ON pt1 FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER pt_trigger_before_p1 BEFORE UPDATE ON pt1 FOR EACH STATEMENT
EXECUTE PROCEDURE process_pt_trigger();

postgres=# UPDATE pt SET a = 5 WHERE a = 4;
UPDATE 1
postgres=# SELECT * FROM pt_trigger ORDER BY 1;
tg_name | tg_table_name | tg_level | tg_when
----------------------+---------------+-----------+---------
pt_trigger_after_p1 | pt1 | STATEMENT | AFTER
pt_trigger_before_p1 | pt1 | STATEMENT | BEFORE
(2 rows)

Actually, this works only by accident (with the current implementation,
the *first* partition's triggers will get fired). If you create another
partition, its per-statement triggers won't get fired. The patches
described above fixes that.

It would be great if you could check if the patches fix the issues.

Also, adding this to the PostgreSQL 10 open items list.

Thanks,
Amit

[1]: /messages/by-id/8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp
/messages/by-id/8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp

Attachments:

0002-Fire-per-statement-triggers-of-inheritance-child-tab.patchtext/x-diff; name=0002-Fire-per-statement-triggers-of-inheritance-child-tab.patchDownload
From f69acdb660e391302caf9cfe4086a4ae7f4122b2 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 13:57:50 +0900
Subject: [PATCH 2/2] Fire per-statement triggers of inheritance child tables

There has been a long-standing decision to not fire the child table's
per-statement triggers even though update/delete on inheritance
hierarchies (including the partitioning case) *do* affect the child
tables along with the parent table mentioned in the statement.
---
 src/backend/executor/nodeModifyTable.c | 32 ++++++++++++++++-------------
 src/test/regress/expected/triggers.out | 37 ++++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 29 ++++++++++++++++++++++++++
 3 files changed, 84 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6cab15646f..cbaf402e62 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1330,19 +1330,21 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 static void
 fireBSTriggers(ModifyTableState *node)
 {
-	/* Fire for the root partitioned table, if any, and return. */
+	int		i;
+
+	/* Fire for the non-leaf targets, including the root partitioned table */
 	if (node->nonleafResultRelInfo)
 	{
-		fireBSTriggersFor(node, node->nonleafResultRelInfo);
-		return;
+		for (i = 0; i < node->mt_numnonleafrels; i++)
+			fireBSTriggersFor(node, node->nonleafResultRelInfo + i);
 	}
 
 	/*
-	 * Fire for the main result relation.  In the partitioned table case,
-	 * the following happens to be the first leaf partition, which we don't
-	 * need to fire triggers for.
+	 * Fire for the leaf targets.  In the non-partitioned inheritance case,
+	 * this includes all the tables in the hierarchy.
 	 */
-	fireBSTriggersFor(node, node->resultRelInfo);
+	for (i = 0; i < node->mt_nplans; i++)
+		fireBSTriggersFor(node, node->resultRelInfo + i);
 }
 
 /*
@@ -1376,19 +1378,21 @@ fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
 static void
 fireASTriggers(ModifyTableState *node)
 {
-	/* Fire for the root partitioned table, if any, and return. */
+	int		i;
+
+	/* Fire for the non-leaf targets, including the root partitioned table */
 	if (node->nonleafResultRelInfo)
 	{
-		fireASTriggersFor(node, node->nonleafResultRelInfo);
-		return;
+		for (i = 0; i < node->mt_numnonleafrels; i++)
+			fireASTriggersFor(node, node->nonleafResultRelInfo + i);
 	}
 
 	/*
-	 * Fire for the main result relation.  In the partitioned table case,
-	 * the following happens to be the first leaf partition, which we don't
-	 * need to fire triggers for.
+	 * Fire for the leaf targets.  In the non-partitioned inheritance case,
+	 * this includes all the tables in the hierarchy.
 	 */
-	fireASTriggersFor(node, node->resultRelInfo);
+	for (i = 0; i < node->mt_nplans; i++)
+		fireASTriggersFor(node, node->resultRelInfo + i);
 }
 
 /*
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index ec0e0c2782..df2726c497 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1793,6 +1793,8 @@ drop table my_table;
 create table parted_stmt_trig (a int) partition by list (a);
 create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
 create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+create table regular_stmt_trig (a int);
+create table regular_stmt_trig_child () inherits (regular_stmt_trig);
 create or replace function trigger_notice() returns trigger as $$
   begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
   $$ language plpgsql;
@@ -1822,6 +1824,24 @@ create trigger trig_del_before before delete on parted_stmt_trig1
   for each statement execute procedure trigger_notice();
 create trigger trig_del_after after delete on parted_stmt_trig1
   for each statement execute procedure trigger_notice();
+-- update/delete triggers on the regular inheritance parent
+create trigger trig_upd_before before update on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+-- update/delete triggers on the regular inheritance child
+create trigger trig_upd_before before update on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
 with ins (partname, a) as (
   insert into parted_stmt_trig values (1) returning tableoid::regclass, a
 ) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
@@ -1836,8 +1856,25 @@ NOTICE:  trigger on parted_stmt_trig AFTER INSERT
 
 update parted_stmt_trig set a = a;
 NOTICE:  trigger on parted_stmt_trig BEFORE UPDATE
+NOTICE:  trigger on parted_stmt_trig1 BEFORE UPDATE
 NOTICE:  trigger on parted_stmt_trig AFTER UPDATE
+NOTICE:  trigger on parted_stmt_trig1 AFTER UPDATE
 delete from parted_stmt_trig;
 NOTICE:  trigger on parted_stmt_trig BEFORE DELETE
+NOTICE:  trigger on parted_stmt_trig1 BEFORE DELETE
 NOTICE:  trigger on parted_stmt_trig AFTER DELETE
+NOTICE:  trigger on parted_stmt_trig1 AFTER DELETE
+insert into regular_stmt_trig values (1);
+insert into regular_stmt_trig_child values (1);
+update regular_stmt_trig set a = a;
+NOTICE:  trigger on regular_stmt_trig BEFORE UPDATE
+NOTICE:  trigger on regular_stmt_trig_child BEFORE UPDATE
+NOTICE:  trigger on regular_stmt_trig AFTER UPDATE
+NOTICE:  trigger on regular_stmt_trig_child AFTER UPDATE
+delete from regular_stmt_trig;
+NOTICE:  trigger on regular_stmt_trig BEFORE DELETE
+NOTICE:  trigger on regular_stmt_trig_child BEFORE DELETE
+NOTICE:  trigger on regular_stmt_trig AFTER DELETE
+NOTICE:  trigger on regular_stmt_trig_child AFTER DELETE
 drop table parted_stmt_trig;
+drop table regular_stmt_trig, regular_stmt_trig_child;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index a767098465..2784fe1077 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1271,6 +1271,9 @@ create table parted_stmt_trig (a int) partition by list (a);
 create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
 create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
 
+create table regular_stmt_trig (a int);
+create table regular_stmt_trig_child () inherits (regular_stmt_trig);
+
 create or replace function trigger_notice() returns trigger as $$
   begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
   $$ language plpgsql;
@@ -1303,6 +1306,26 @@ create trigger trig_del_before before delete on parted_stmt_trig1
 create trigger trig_del_after after delete on parted_stmt_trig1
   for each statement execute procedure trigger_notice();
 
+-- update/delete triggers on the regular inheritance parent
+create trigger trig_upd_before before update on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on regular_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+-- update/delete triggers on the regular inheritance child
+create trigger trig_upd_before before update on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on regular_stmt_trig_child
+  for each statement execute procedure trigger_notice();
+
 with ins (partname, a) as (
   insert into parted_stmt_trig values (1) returning tableoid::regclass, a
 ) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
@@ -1310,4 +1333,10 @@ with ins (partname, a) as (
 update parted_stmt_trig set a = a;
 delete from parted_stmt_trig;
 
+insert into regular_stmt_trig values (1);
+insert into regular_stmt_trig_child values (1);
+update regular_stmt_trig set a = a;
+delete from regular_stmt_trig;
+
 drop table parted_stmt_trig;
+drop table regular_stmt_trig, regular_stmt_trig_child;
-- 
2.11.0

0001-Fire-per-statement-triggers-of-partitioned-tables.patchtext/x-diff; name=0001-Fire-per-statement-triggers-of-partitioned-tables.patchDownload
From 9c7543615ccb05c004591a9176f818413df7ea9d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH 1/2] Fire per-statement triggers of partitioned tables

Current implementation completely misses them, because it assumes
that no ResultRelInfos need to be created for partitioned tables,
whereas they *are* needed to fire the per-statment triggers, which
we *do* allow to be defined on the partitioned tables.

Reported By: Rajkumar Raghuwanshi
Patch by: Amit Langote
Report: https://www.postgresql.org/message-id/CAKcux6%3DwYospCRY2J4XEFuVy0L41S%3Dfic7rmkbsU-GXhhSbmBg%40mail.gmail.com
---
 src/backend/executor/execMain.c         | 33 ++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 66 ++++++++++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c           |  1 +
 src/backend/nodes/outfuncs.c            |  1 +
 src/backend/nodes/readfuncs.c           |  1 +
 src/backend/optimizer/plan/createplan.c |  1 +
 src/backend/optimizer/plan/setrefs.c    |  4 ++
 src/include/nodes/execnodes.h           | 11 ++++++
 src/include/nodes/plannodes.h           |  2 +
 src/test/regress/expected/triggers.out  | 54 +++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql       | 48 ++++++++++++++++++++++++
 11 files changed, 210 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5c12fb457d..586b396b3e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -861,18 +861,35 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 		/*
 		 * In the partitioned result relation case, lock the non-leaf result
-		 * relations too.  We don't however need ResultRelInfos for them.
+		 * relations too.  We also need ResultRelInfos so that per-statement
+		 * triggers defined on these relations can be fired.
 		 */
 		if (plannedstmt->nonleafResultRelations)
 		{
+			numResultRelations =
+							list_length(plannedstmt->nonleafResultRelations);
+
+			resultRelInfos = (ResultRelInfo *)
+						palloc(numResultRelations * sizeof(ResultRelInfo));
+			resultRelInfo = resultRelInfos;
 			foreach(l, plannedstmt->nonleafResultRelations)
 			{
 				Index		resultRelationIndex = lfirst_int(l);
 				Oid			resultRelationOid;
+				Relation	resultRelation;
 
 				resultRelationOid = getrelid(resultRelationIndex, rangeTable);
-				LockRelationOid(resultRelationOid, RowExclusiveLock);
+				resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+				InitResultRelInfo(resultRelInfo,
+								  resultRelation,
+								  resultRelationIndex,
+								  NULL,
+								  estate->es_instrument);
+				resultRelInfo++;
 			}
+
+			estate->es_nonleaf_result_relations = resultRelInfos;
+			estate->es_num_nonleaf_result_relations = numResultRelations;
 		}
 	}
 	else
@@ -883,6 +900,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_result_relations = NULL;
 		estate->es_num_result_relations = 0;
 		estate->es_result_relation_info = NULL;
+		estate->es_nonleaf_result_relations = NULL;
+		estate->es_num_nonleaf_result_relations = 0;
 	}
 
 	/*
@@ -1566,6 +1585,16 @@ ExecEndPlan(PlanState *planstate, EState *estate)
 	}
 
 	/*
+	 * close any non-leaf target relations
+	 */
+	resultRelInfo = estate->es_nonleaf_result_relations;
+	for (i = estate->es_num_nonleaf_result_relations; i > 0; i--)
+	{
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		resultRelInfo++;
+	}
+
+	/*
 	 * likewise close any trigger target relations
 	 */
 	foreach(l, estate->es_trig_target_relations)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 71e3b8ec2d..6cab15646f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -62,6 +62,8 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
 					 EState *estate,
 					 bool canSetTag,
 					 TupleTableSlot **returning);
+static void fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
+static void fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
 
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1328,19 +1330,39 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 static void
 fireBSTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->nonleafResultRelInfo)
+	{
+		fireBSTriggersFor(node, node->nonleafResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireBSTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process BEFORE EACH STATEMENT triggers for one result relation
+ */
+static void
+fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
-			ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSInsertTriggers(node->ps.state, resultRelInfo);
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecBSUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
+				ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecBSDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1354,19 +1376,39 @@ fireBSTriggers(ModifyTableState *node)
 static void
 fireASTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->nonleafResultRelInfo)
+	{
+		fireASTriggersFor(node, node->nonleafResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireASTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process AFTER EACH STATEMENT triggers for one result relation
+ */
+static void
+fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecASUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
-			ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
+				ExecASUpdateTriggers(node->ps.state, resultRelInfo);
+			ExecASInsertTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecASUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecASUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecASDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecASDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1628,6 +1670,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	ModifyTableState *mtstate;
 	CmdType		operation = node->operation;
 	int			nplans = list_length(node->plans);
+	int			num_nonleafrels = list_length(node->partitioned_rels);
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	TupleDesc	tupDesc;
@@ -1652,6 +1695,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+	mtstate->mt_numnonleafrels = num_nonleafrels;
+	mtstate->nonleafResultRelInfo = estate->es_nonleaf_result_relations +
+												node->nonleafResultRelIndex;
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
 	mtstate->mt_nplans = nplans;
 	mtstate->mt_onconflict = node->onConflictAction;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 00a0fed23d..e425a57feb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -205,6 +205,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
+	COPY_SCALAR_FIELD(nonleafResultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 28cef85579..a501f250ba 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -350,6 +350,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
+	WRITE_INT_FIELD(nonleafResultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a883220a49..aa6f54870c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1548,6 +1548,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
+	READ_INT_FIELD(nonleafResultRelIndex);
 	READ_NODE_FIELD(plans);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 95e6eb7d28..de2c77a22c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6437,6 +6437,7 @@ make_modifytable(PlannerInfo *root,
 	node->partitioned_rels = partitioned_rels;
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
+	node->nonleafResultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
 	if (!onconflict)
 	{
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1278371b65..27a145187e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -883,7 +883,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				 * If the main target relation is a partitioned table, the
 				 * following list contains the RT indexes of partitioned child
 				 * relations, which are not included in the above list.
+				 * Set nonleafResultRelIndex to reflect the starting position
+				 * in the global list.
 				 */
+				splan->nonleafResultRelIndex =
+							list_length(root->glob->nonleafResultRelations);
 				root->glob->nonleafResultRelations =
 					list_concat(root->glob->nonleafResultRelations,
 								list_copy(splan->partitioned_rels));
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4330a851c3..3f801b9b0c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -422,6 +422,14 @@ typedef struct EState
 	int			es_num_result_relations;		/* length of array */
 	ResultRelInfo *es_result_relation_info;		/* currently active array elt */
 
+	/*
+	 * Info about non-leaf target tables during update/delete on partitioned
+	 * tables.  They are required only to fire the per-statement triggers
+	 * defined on the non-leaf tables in a partitioned table hierarchy.
+	 */
+	ResultRelInfo *es_nonleaf_result_relations; /* array of ResultRelInfos */
+	int			es_num_nonleaf_result_relations;		/* length of array */
+
 	/* Stuff used for firing triggers: */
 	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
 	TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
@@ -914,6 +922,9 @@ typedef struct ModifyTableState
 	int			mt_nplans;		/* number of plans in the array */
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	ResultRelInfo *resultRelInfo;		/* per-subplan target relations */
+	int			mt_numnonleafrels;	/* number of non-leaf result rels in the
+									 * below array */
+	ResultRelInfo *nonleafResultRelInfo;	/* non-leaf target relations */
 	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cba915572e..474343d340 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -211,6 +211,8 @@ typedef struct ModifyTable
 	List	   *partitioned_rels;
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
+	int			nonleafResultRelIndex; /* index of first nonleaf resultRel in
+										* plan's list */
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 4b0b3b7c42..ec0e0c2782 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1787,3 +1787,57 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+create or replace function trigger_notice() returns trigger as $$
+  begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
+  $$ language plpgsql;
+-- insert/update/delete triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+-- insert/update/delete triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+with ins (partname, a) as (
+  insert into parted_stmt_trig values (1) returning tableoid::regclass, a
+) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT
+     tableoid      | a 
+-------------------+---
+ parted_stmt_trig2 | 2
+(1 row)
+
+update parted_stmt_trig set a = a;
+NOTICE:  trigger on parted_stmt_trig BEFORE UPDATE
+NOTICE:  trigger on parted_stmt_trig AFTER UPDATE
+delete from parted_stmt_trig;
+NOTICE:  trigger on parted_stmt_trig BEFORE DELETE
+NOTICE:  trigger on parted_stmt_trig AFTER DELETE
+drop table parted_stmt_trig;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 4473ce0518..a767098465 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1263,3 +1263,51 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+
+create or replace function trigger_notice() returns trigger as $$
+  begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
+  $$ language plpgsql;
+
+-- insert/update/delete triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+-- insert/update/delete triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+
+with ins (partname, a) as (
+  insert into parted_stmt_trig values (1) returning tableoid::regclass, a
+) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
+
+update parted_stmt_trig set a = a;
+delete from parted_stmt_trig;
+
+drop table parted_stmt_trig;
-- 
2.11.0

#298Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#297)
Re: Declarative partitioning - another take

On Mon, Apr 24, 2017 at 4:13 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Hi Rajkumar,

It would be great if you could check if the patches fix the issues.

Hi Amit,

Thanks for looking into it. I have applied fixes and checked for triggers.
I could see difference in behaviour of statement triggers for INSERT and
UPDATE, for insert only root partition triggers are getting fired but for
update root as well as child partition table triggers both getting fired.
is this expected??

Below are steps to reproduce.

CREATE TABLE pt (a INT, b INT) PARTITION BY RANGE(a);
CREATE TABLE pt1 PARTITION OF pt FOR VALUES FROM (1) to (6);
CREATE TABLE pt2 PARTITION OF pt FOR VALUES FROM (6) to (11);
INSERT INTO pt (a,b) SELECT i,i FROM generate_series(1,7)i;

CREATE TABLE pt_trigger(TG_NAME varchar,TG_TABLE_NAME varchar,TG_LEVEL
varchar,TG_WHEN varchar,a_old int,a_new int,b_old int,b_new int);
CREATE FUNCTION process_pt_trigger() RETURNS TRIGGER AS $ttp$
BEGIN
IF (TG_OP = 'INSERT') THEN
IF (TG_LEVEL = 'STATEMENT') THEN INSERT INTO pt_trigger
SELECT TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN,NULL,NULL,NULL,NULL; END IF;
IF (TG_LEVEL = 'ROW') THEN INSERT INTO pt_trigger SELECT
TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN,NULL,NEW.a,NULL,NEW.b; END IF;
RETURN NEW;
END IF;
IF (TG_OP = 'UPDATE') THEN
IF (TG_LEVEL = 'STATEMENT') THEN INSERT INTO pt_trigger
SELECT TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN,NULL,NULL,NULL,NULL; END IF;
IF (TG_LEVEL = 'ROW') THEN INSERT INTO pt_trigger SELECT
TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN,OLD.a,NEW.a,OLD.b,NEW.b; END IF;
RETURN NEW;
END IF;
END;
$ttp$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_test11 AFTER INSERT OR UPDATE ON pt FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test12 AFTER INSERT OR UPDATE ON pt1 FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test13 AFTER INSERT OR UPDATE ON pt2 FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();

CREATE TRIGGER trigger_test21 BEFORE INSERT OR UPDATE ON pt FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test22 BEFORE INSERT OR UPDATE ON pt1 FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test23 BEFORE INSERT OR UPDATE ON pt2 FOR EACH
STATEMENT EXECUTE PROCEDURE process_pt_trigger();

CREATE TRIGGER trigger_test32 AFTER INSERT OR UPDATE ON pt1 FOR EACH ROW
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test33 AFTER INSERT OR UPDATE ON pt2 FOR EACH ROW
EXECUTE PROCEDURE process_pt_trigger();

CREATE TRIGGER trigger_test42 BEFORE INSERT OR UPDATE ON pt1 FOR EACH ROW
EXECUTE PROCEDURE process_pt_trigger();
CREATE TRIGGER trigger_test43 BEFORE INSERT OR UPDATE ON pt2 FOR EACH ROW
EXECUTE PROCEDURE process_pt_trigger();

postgres=# INSERT INTO pt (a,b) VALUES (8,8);
INSERT 0 1
postgres=# SELECT * FROM pt_trigger;
tg_name | tg_table_name | tg_level | tg_when | a_old | a_new |
b_old | b_new
----------------+---------------+-----------+---------+-------+-------+-------+-------
trigger_test21 | pt | STATEMENT | BEFORE | |
| |
trigger_test43 | pt2 | ROW | BEFORE | | 8
| | 8
trigger_test33 | pt2 | ROW | AFTER | | 8
| | 8
trigger_test11 | pt | STATEMENT | AFTER | |
| |
(4 rows)

postgres=# TRUNCATE TABLE pt_trigger;
TRUNCATE TABLE
postgres=# UPDATE pt SET a = 2 WHERE a = 1;
UPDATE 1
postgres=# SELECT * FROM pt_trigger;
tg_name | tg_table_name | tg_level | tg_when | a_old | a_new |
b_old | b_new
----------------+---------------+-----------+---------+-------+-------+-------+-------
trigger_test21 | pt | STATEMENT | BEFORE | |
| |
trigger_test22 | pt1 | STATEMENT | BEFORE | |
| |
trigger_test42 | pt1 | ROW | BEFORE | 1 | 2 |
1 | 1
trigger_test32 | pt1 | ROW | AFTER | 1 | 2 |
1 | 1
trigger_test11 | pt | STATEMENT | AFTER | |
| |
trigger_test12 | pt1 | STATEMENT | AFTER | |
| |
(6 rows)

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB

#299Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#297)
Re: Declarative partitioning - another take

On Mon, Apr 24, 2017 at 6:43 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The reason it doesn't work is that we do not allocate ResultRelInfos for
partitioned tables (not even for the root partitioned table in the
update/delete cases), because the current implementation assumes they are
not required. That's fine only so long as we consider that no rows are
inserted into them, no indexes need to be updated, and that no row-level
triggers are to be fired. But it misses the fact that we do allow
statement-level triggers on partitioned tables. So, we should fix things
such that ResultRelInfos are allocated so that any statement-level
triggers are fired. But there are following questions to consider:

1. Should we consider only the root partitioned table or all partitioned
tables in a given hierarchy, including the child partitioned tables?
Note that this is related to a current limitation of updating/deleting
inheritance hierarchies that we do not currently fire per-statement
triggers of the child tables. See the TODO item in wiki:
https://wiki.postgresql.org/wiki/Todo#Triggers, which says: "When
statement-level triggers are defined on a parent table, have them fire
only on the parent table, and fire child table triggers only where
appropriate"

2. Do we apply the same to inserts on the partitioned tables? Since
insert on a partitioned table implicitly affects its partitions, one
might say that we would need to fire per-statement insert triggers of
all the partitions.

It seems to me that it doesn't make a lot of sense to fire the
triggers for some tables involved in the hierarchy and not others. I
suppose the consistent thing to do here is to make sure that we fire
the statement triggers for all tables in the partitioning hierarchy
for all operations (insert, update, delete, etc.).

TBH, I don't like that very much. I'd rather fire the triggers only
for the table actually named in the query and skip all the others,
mostly because it seems like it would be faster and less likely to
block future optimizations; eventually, I'd like to consider not even
locking the children we're not touching, but that's not going to work
if we have to check them all for triggers. So what I'd prefer -- on
the totally unprincipled basis that it would let us improve
performance in the future -- if you operate on a partition directly,
you fire the partition's triggers, but if you operate on the parent,
only the parent's triggers fire.

How heretical is that idea?

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

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

#300Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#299)
Re: Declarative partitioning - another take

On 2017/04/26 3:58, Robert Haas wrote:

On Mon, Apr 24, 2017 at 6:43 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

The reason it doesn't work is that we do not allocate ResultRelInfos for
partitioned tables (not even for the root partitioned table in the
update/delete cases), because the current implementation assumes they are
not required. That's fine only so long as we consider that no rows are
inserted into them, no indexes need to be updated, and that no row-level
triggers are to be fired. But it misses the fact that we do allow
statement-level triggers on partitioned tables. So, we should fix things
such that ResultRelInfos are allocated so that any statement-level
triggers are fired. But there are following questions to consider:

1. Should we consider only the root partitioned table or all partitioned
tables in a given hierarchy, including the child partitioned tables?
Note that this is related to a current limitation of updating/deleting
inheritance hierarchies that we do not currently fire per-statement
triggers of the child tables. See the TODO item in wiki:
https://wiki.postgresql.org/wiki/Todo#Triggers, which says: "When
statement-level triggers are defined on a parent table, have them fire
only on the parent table, and fire child table triggers only where
appropriate"

2. Do we apply the same to inserts on the partitioned tables? Since
insert on a partitioned table implicitly affects its partitions, one
might say that we would need to fire per-statement insert triggers of
all the partitions.

It seems to me that it doesn't make a lot of sense to fire the
triggers for some tables involved in the hierarchy and not others. I
suppose the consistent thing to do here is to make sure that we fire
the statement triggers for all tables in the partitioning hierarchy
for all operations (insert, update, delete, etc.).

TBH, I don't like that very much. I'd rather fire the triggers only
for the table actually named in the query and skip all the others,
mostly because it seems like it would be faster and less likely to
block future optimizations; eventually, I'd like to consider not even
locking the children we're not touching, but that's not going to work
if we have to check them all for triggers. So what I'd prefer -- on
the totally unprincipled basis that it would let us improve
performance in the future -- if you operate on a partition directly,
you fire the partition's triggers, but if you operate on the parent,
only the parent's triggers fire.

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Thanks,
Amit

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

#301Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#298)
Re: Declarative partitioning - another take

Hi,

Thanks for testing.

On 2017/04/25 19:03, Rajkumar Raghuwanshi wrote:

Thanks for looking into it. I have applied fixes and checked for triggers.
I could see difference in behaviour of statement triggers for INSERT and
UPDATE, for insert only root partition triggers are getting fired but for
update root as well as child partition table triggers both getting fired.
is this expected??

Yes, because I didn't implement anything for the insert case yet. I posed
a question whether to fire partitions' per-statement triggers when
inserting data through the root table.

Robert replied [1]/messages/by-id/CA+TgmoatBYy8Hyi3cYR1rFrCkD2NM4ZLZcck4QDGvH=HddfDwA@mail.gmail.com that it would be desirable to not fire partitions'
per-statement triggers if the root table is mentioned in the query; only
fire their per-row triggers if any. It already works that way for
inserts, and applying only 0001 will get you the same for update/delete.
Patch 0002 is to enable firing partition's per-statement triggers even if
the root table is mentioned in the query, but it implemented the same only
for the update/delete cases. If we decide that that's the right thing to
do, then I will implement the same behavior for the insert case too.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoatBYy8Hyi3cYR1rFrCkD2NM4ZLZcck4QDGvH=HddfDwA@mail.gmail.com
/messages/by-id/CA+TgmoatBYy8Hyi3cYR1rFrCkD2NM4ZLZcck4QDGvH=HddfDwA@mail.gmail.com

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

#302Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Langote (#301)
Re: Declarative partitioning - another take

On 26 April 2017 at 00:28, Robert Haas <robertmhaas@gmail.com> wrote:

So what I'd prefer -- on
the totally unprincipled basis that it would let us improve
performance in the future -- if you operate on a partition directly,
you fire the partition's triggers, but if you operate on the parent,
only the parent's triggers fire.

I would also opt for this behaviour.

Thanks,
-Amit Khandekar
EnterpriseDB Corporation
The Postgres Database Company

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

#303Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#300)
Re: Declarative partitioning - another take

On Tue, Apr 25, 2017 at 10:34 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Do we need to update the documentation?

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

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

#304Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#303)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/04/27 1:52, Robert Haas wrote:

On Tue, Apr 25, 2017 at 10:34 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

Thanks,
Amit

Attachments:

0001-Fire-per-statement-triggers-of-partitioned-tables.patchtext/x-diff; name=0001-Fire-per-statement-triggers-of-partitioned-tables.patchDownload
From c8b785a77a2a193906967762066e03e09de80442 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH 1/2] Fire per-statement triggers of partitioned tables

Current implementation completely misses them, because it assumes
that no ResultRelInfos need to be created for partitioned tables,
whereas they *are* needed to fire the per-statment triggers, which
we *do* allow to be defined on the partitioned tables.

Reported By: Rajkumar Raghuwanshi
Patch by: Amit Langote
Report: https://www.postgresql.org/message-id/CAKcux6%3DwYospCRY2J4XEFuVy0L41S%3Dfic7rmkbsU-GXhhSbmBg%40mail.gmail.com
---
 doc/src/sgml/trigger.sgml               | 26 ++++++++-----
 src/backend/executor/execMain.c         | 33 ++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 66 ++++++++++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c           |  1 +
 src/backend/nodes/outfuncs.c            |  1 +
 src/backend/nodes/readfuncs.c           |  1 +
 src/backend/optimizer/plan/createplan.c |  1 +
 src/backend/optimizer/plan/setrefs.c    |  4 ++
 src/include/nodes/execnodes.h           | 11 ++++++
 src/include/nodes/plannodes.h           |  2 +
 src/test/regress/expected/triggers.out  | 54 +++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql       | 48 ++++++++++++++++++++++++
 12 files changed, 227 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 2a718d7f47..5771bd5fdc 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -33,7 +33,8 @@
    <para>
     A trigger is a specification that the database should automatically
     execute a particular function whenever a certain type of operation is
-    performed.  Triggers can be attached to tables, views, and foreign tables.
+    performed.  Triggers can be attached to tables (partitioned or not),
+    views, and foreign tables.
   </para>
 
   <para>
@@ -111,14 +112,21 @@
     Statement-level <literal>BEFORE</> triggers naturally fire before the
     statement starts to do anything, while statement-level <literal>AFTER</>
     triggers fire at the very end of the statement.  These types of
-    triggers may be defined on tables or views.  Row-level <literal>BEFORE</>
-    triggers fire immediately before a particular row is operated on,
-    while row-level <literal>AFTER</> triggers fire at the end of the
-    statement (but before any statement-level <literal>AFTER</> triggers).
-    These types of triggers may only be defined on tables and foreign tables.
-    Row-level <literal>INSTEAD OF</> triggers may only be defined on views,
-    and fire immediately as each row in the view is identified as needing to
-    be operated on.
+    triggers may be defined on tables, views, or foreign tables.  Row-level
+    <literal>BEFORE</> triggers fire immediately before a particular row is
+    operated on, while row-level <literal>AFTER</> triggers fire at the end of
+    the statement (but before any statement-level <literal>AFTER</> triggers).
+    These types of triggers may only be defined on non-partitioned tables and
+    foreign tables.  Row-level <literal>INSTEAD OF</> triggers may only be
+    defined on views, and fire immediately as each row in the view is
+    identified as needing to be operated on.
+   </para>
+
+   <para>
+    A statement-level trigger defined on partitioned tables is fired only
+    once for the table itself, not once for every table in the partitioning
+    hierarchy.  However, row-level triggers of any affected leaf partitions
+    will be fired.
    </para>
 
    <para>
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5c12fb457d..586b396b3e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -861,18 +861,35 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 		/*
 		 * In the partitioned result relation case, lock the non-leaf result
-		 * relations too.  We don't however need ResultRelInfos for them.
+		 * relations too.  We also need ResultRelInfos so that per-statement
+		 * triggers defined on these relations can be fired.
 		 */
 		if (plannedstmt->nonleafResultRelations)
 		{
+			numResultRelations =
+							list_length(plannedstmt->nonleafResultRelations);
+
+			resultRelInfos = (ResultRelInfo *)
+						palloc(numResultRelations * sizeof(ResultRelInfo));
+			resultRelInfo = resultRelInfos;
 			foreach(l, plannedstmt->nonleafResultRelations)
 			{
 				Index		resultRelationIndex = lfirst_int(l);
 				Oid			resultRelationOid;
+				Relation	resultRelation;
 
 				resultRelationOid = getrelid(resultRelationIndex, rangeTable);
-				LockRelationOid(resultRelationOid, RowExclusiveLock);
+				resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
+				InitResultRelInfo(resultRelInfo,
+								  resultRelation,
+								  resultRelationIndex,
+								  NULL,
+								  estate->es_instrument);
+				resultRelInfo++;
 			}
+
+			estate->es_nonleaf_result_relations = resultRelInfos;
+			estate->es_num_nonleaf_result_relations = numResultRelations;
 		}
 	}
 	else
@@ -883,6 +900,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_result_relations = NULL;
 		estate->es_num_result_relations = 0;
 		estate->es_result_relation_info = NULL;
+		estate->es_nonleaf_result_relations = NULL;
+		estate->es_num_nonleaf_result_relations = 0;
 	}
 
 	/*
@@ -1566,6 +1585,16 @@ ExecEndPlan(PlanState *planstate, EState *estate)
 	}
 
 	/*
+	 * close any non-leaf target relations
+	 */
+	resultRelInfo = estate->es_nonleaf_result_relations;
+	for (i = estate->es_num_nonleaf_result_relations; i > 0; i--)
+	{
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		resultRelInfo++;
+	}
+
+	/*
 	 * likewise close any trigger target relations
 	 */
 	foreach(l, estate->es_trig_target_relations)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 71e3b8ec2d..6cab15646f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -62,6 +62,8 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
 					 EState *estate,
 					 bool canSetTag,
 					 TupleTableSlot **returning);
+static void fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
+static void fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
 
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1328,19 +1330,39 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 static void
 fireBSTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->nonleafResultRelInfo)
+	{
+		fireBSTriggersFor(node, node->nonleafResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireBSTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process BEFORE EACH STATEMENT triggers for one result relation
+ */
+static void
+fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
-			ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSInsertTriggers(node->ps.state, resultRelInfo);
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecBSUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
+				ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecBSDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1354,19 +1376,39 @@ fireBSTriggers(ModifyTableState *node)
 static void
 fireASTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->nonleafResultRelInfo)
+	{
+		fireASTriggersFor(node, node->nonleafResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireASTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process AFTER EACH STATEMENT triggers for one result relation
+ */
+static void
+fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecASUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
-			ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
+				ExecASUpdateTriggers(node->ps.state, resultRelInfo);
+			ExecASInsertTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecASUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecASUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecASDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecASDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1628,6 +1670,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	ModifyTableState *mtstate;
 	CmdType		operation = node->operation;
 	int			nplans = list_length(node->plans);
+	int			num_nonleafrels = list_length(node->partitioned_rels);
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	TupleDesc	tupDesc;
@@ -1652,6 +1695,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+	mtstate->mt_numnonleafrels = num_nonleafrels;
+	mtstate->nonleafResultRelInfo = estate->es_nonleaf_result_relations +
+												node->nonleafResultRelIndex;
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
 	mtstate->mt_nplans = nplans;
 	mtstate->mt_onconflict = node->onConflictAction;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 00a0fed23d..e425a57feb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -205,6 +205,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
+	COPY_SCALAR_FIELD(nonleafResultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 28cef85579..a501f250ba 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -350,6 +350,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
+	WRITE_INT_FIELD(nonleafResultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a883220a49..aa6f54870c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1548,6 +1548,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
+	READ_INT_FIELD(nonleafResultRelIndex);
 	READ_NODE_FIELD(plans);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 95e6eb7d28..de2c77a22c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6437,6 +6437,7 @@ make_modifytable(PlannerInfo *root,
 	node->partitioned_rels = partitioned_rels;
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
+	node->nonleafResultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
 	if (!onconflict)
 	{
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1278371b65..27a145187e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -883,7 +883,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				 * If the main target relation is a partitioned table, the
 				 * following list contains the RT indexes of partitioned child
 				 * relations, which are not included in the above list.
+				 * Set nonleafResultRelIndex to reflect the starting position
+				 * in the global list.
 				 */
+				splan->nonleafResultRelIndex =
+							list_length(root->glob->nonleafResultRelations);
 				root->glob->nonleafResultRelations =
 					list_concat(root->glob->nonleafResultRelations,
 								list_copy(splan->partitioned_rels));
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4330a851c3..3f801b9b0c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -422,6 +422,14 @@ typedef struct EState
 	int			es_num_result_relations;		/* length of array */
 	ResultRelInfo *es_result_relation_info;		/* currently active array elt */
 
+	/*
+	 * Info about non-leaf target tables during update/delete on partitioned
+	 * tables.  They are required only to fire the per-statement triggers
+	 * defined on the non-leaf tables in a partitioned table hierarchy.
+	 */
+	ResultRelInfo *es_nonleaf_result_relations; /* array of ResultRelInfos */
+	int			es_num_nonleaf_result_relations;		/* length of array */
+
 	/* Stuff used for firing triggers: */
 	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
 	TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
@@ -914,6 +922,9 @@ typedef struct ModifyTableState
 	int			mt_nplans;		/* number of plans in the array */
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	ResultRelInfo *resultRelInfo;		/* per-subplan target relations */
+	int			mt_numnonleafrels;	/* number of non-leaf result rels in the
+									 * below array */
+	ResultRelInfo *nonleafResultRelInfo;	/* non-leaf target relations */
 	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cba915572e..474343d340 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -211,6 +211,8 @@ typedef struct ModifyTable
 	List	   *partitioned_rels;
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
+	int			nonleafResultRelIndex; /* index of first nonleaf resultRel in
+										* plan's list */
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 4b0b3b7c42..ec0e0c2782 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1787,3 +1787,57 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+create or replace function trigger_notice() returns trigger as $$
+  begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
+  $$ language plpgsql;
+-- insert/update/delete triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+-- insert/update/delete triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+with ins (partname, a) as (
+  insert into parted_stmt_trig values (1) returning tableoid::regclass, a
+) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT
+     tableoid      | a 
+-------------------+---
+ parted_stmt_trig2 | 2
+(1 row)
+
+update parted_stmt_trig set a = a;
+NOTICE:  trigger on parted_stmt_trig BEFORE UPDATE
+NOTICE:  trigger on parted_stmt_trig AFTER UPDATE
+delete from parted_stmt_trig;
+NOTICE:  trigger on parted_stmt_trig BEFORE DELETE
+NOTICE:  trigger on parted_stmt_trig AFTER DELETE
+drop table parted_stmt_trig;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 4473ce0518..a767098465 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1263,3 +1263,51 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+
+create or replace function trigger_notice() returns trigger as $$
+  begin raise notice 'trigger on % % %', TG_TABLE_NAME, TG_WHEN, TG_OP; return null; end;
+  $$ language plpgsql;
+
+-- insert/update/delete triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+-- insert/update/delete triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig1
+  for each statement execute procedure trigger_notice();
+
+with ins (partname, a) as (
+  insert into parted_stmt_trig values (1) returning tableoid::regclass, a
+) insert into parted_stmt_trig select a+1 from ins returning tableoid::regclass, a;
+
+update parted_stmt_trig set a = a;
+delete from parted_stmt_trig;
+
+drop table parted_stmt_trig;
-- 
2.11.0

#305Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#304)
Re: Declarative partitioning - another take

On Wed, Apr 26, 2017 at 9:30 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

Looks reasonable, but I was thinking you might also update the section
which contrasts inheritance-based partitioning with declarative
partitioning.

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

It seems better not to create any ResultRelInfos that we don't
actually need, so +1 for such a revision to the patch.

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

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

#306David Fetter
david@fetter.org
In reply to: Amit Langote (#304)
Re: Declarative partitioning - another take

On Thu, Apr 27, 2017 at 10:30:54AM +0900, Amit Langote wrote:

On 2017/04/27 1:52, Robert Haas wrote:

On Tue, Apr 25, 2017 at 10:34 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

Did I notice correctly that there's no way to handle transition tables
for partitions in AFTER STATEMENT triggers?

If not, I'm not suggesting that this be added at this late date, but
we might want to document that.

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

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

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

#307Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#305)
1 attachment(s)
Re: Declarative partitioning - another take

Thanks for taking a look.

On 2017/04/28 5:24, Robert Haas wrote:

On Wed, Apr 26, 2017 at 9:30 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

Looks reasonable, but I was thinking you might also update the section
which contrasts inheritance-based partitioning with declarative
partitioning.

It seems to me that there is no difference in behavior between
inheritance-based and declarative partitioning as far as statement-level
triggers are concerned (at least currently). In both the cases, we fire
statement-level triggers only for the table specified in the command.

Maybe, we will fix things someday so that statement-level triggers will be
fired for all the tables in a inheritance hierarchy when the root parent
is updated or deleted, but that's currently not true. We may never
implement that behavior for declarative partitioned tables though, so
there will be a difference if and when we implement the former.

Am I missing something?

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

It seems better not to create any ResultRelInfos that we don't
actually need, so +1 for such a revision to the patch.

OK, done. It took a bit more work than I thought.

Updated patch attached.

Thanks,
Amit

Attachments:

0001-Fire-per-statement-triggers-of-partitioned-tables.patchtext/x-diff; name=0001-Fire-per-statement-triggers-of-partitioned-tables.patchDownload
From a8b584f09bc02b6c16c33e816fc12c7e443dd65c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH] Fire per-statement triggers of partitioned tables

Current implementation completely misses them, because it assumes
that no ResultRelInfos need to be created for partitioned tables,
whereas they *are* needed to fire the per-statment triggers, which
we *do* allow to be defined on the partitioned tables.

Reported By: Rajkumar Raghuwanshi
Patch by: Amit Langote
Report: https://www.postgresql.org/message-id/CAKcux6%3DwYospCRY2J4XEFuVy0L41S%3Dfic7rmkbsU-GXhhSbmBg%40mail.gmail.com
---
 doc/src/sgml/trigger.sgml               | 26 +++++++----
 src/backend/executor/execMain.c         | 55 ++++++++++++++++++++--
 src/backend/executor/nodeModifyTable.c  | 64 ++++++++++++++++++++++----
 src/backend/nodes/copyfuncs.c           |  2 +
 src/backend/nodes/outfuncs.c            |  3 ++
 src/backend/nodes/readfuncs.c           |  2 +
 src/backend/optimizer/plan/createplan.c |  1 +
 src/backend/optimizer/plan/planner.c    |  3 ++
 src/backend/optimizer/plan/setrefs.c    | 19 ++++++--
 src/include/nodes/execnodes.h           | 12 +++++
 src/include/nodes/plannodes.h           | 13 +++++-
 src/include/nodes/relation.h            |  1 +
 src/test/regress/expected/triggers.out  | 81 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql       | 70 ++++++++++++++++++++++++++++
 14 files changed, 323 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 2a718d7f47..5771bd5fdc 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -33,7 +33,8 @@
    <para>
     A trigger is a specification that the database should automatically
     execute a particular function whenever a certain type of operation is
-    performed.  Triggers can be attached to tables, views, and foreign tables.
+    performed.  Triggers can be attached to tables (partitioned or not),
+    views, and foreign tables.
   </para>
 
   <para>
@@ -111,14 +112,21 @@
     Statement-level <literal>BEFORE</> triggers naturally fire before the
     statement starts to do anything, while statement-level <literal>AFTER</>
     triggers fire at the very end of the statement.  These types of
-    triggers may be defined on tables or views.  Row-level <literal>BEFORE</>
-    triggers fire immediately before a particular row is operated on,
-    while row-level <literal>AFTER</> triggers fire at the end of the
-    statement (but before any statement-level <literal>AFTER</> triggers).
-    These types of triggers may only be defined on tables and foreign tables.
-    Row-level <literal>INSTEAD OF</> triggers may only be defined on views,
-    and fire immediately as each row in the view is identified as needing to
-    be operated on.
+    triggers may be defined on tables, views, or foreign tables.  Row-level
+    <literal>BEFORE</> triggers fire immediately before a particular row is
+    operated on, while row-level <literal>AFTER</> triggers fire at the end of
+    the statement (but before any statement-level <literal>AFTER</> triggers).
+    These types of triggers may only be defined on non-partitioned tables and
+    foreign tables.  Row-level <literal>INSTEAD OF</> triggers may only be
+    defined on views, and fire immediately as each row in the view is
+    identified as needing to be operated on.
+   </para>
+
+   <para>
+    A statement-level trigger defined on partitioned tables is fired only
+    once for the table itself, not once for every table in the partitioning
+    hierarchy.  However, row-level triggers of any affected leaf partitions
+    will be fired.
    </para>
 
    <para>
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5c12fb457d..cdb1a6a5f5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -861,17 +861,52 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 		/*
 		 * In the partitioned result relation case, lock the non-leaf result
-		 * relations too.  We don't however need ResultRelInfos for them.
+		 * relations too.  A subset of these are the roots of respective
+		 * partitioned tables, for which we also allocate ResulRelInfos.
 		 */
+		estate->es_root_result_relations = NULL;
+		estate->es_num_root_result_relations = 0;
 		if (plannedstmt->nonleafResultRelations)
 		{
+			int		num_roots = list_length(plannedstmt->rootResultRelations);
+
+			/*
+			 * Firstly, build ResultRelInfos for all the partitioned table
+			 * roots, because we will need them to fire the statement-level
+			 * triggers, if any.
+			 */
+			resultRelInfos = (ResultRelInfo *)
+									palloc(num_roots * sizeof(ResultRelInfo));
+			resultRelInfo = resultRelInfos;
+			foreach(l, plannedstmt->rootResultRelations)
+			{
+				Index		resultRelIndex = lfirst_int(l);
+				Oid			resultRelOid;
+				Relation	resultRelDesc;
+
+				resultRelOid = getrelid(resultRelIndex, rangeTable);
+				resultRelDesc = heap_open(resultRelOid, RowExclusiveLock);
+				InitResultRelInfo(resultRelInfo,
+								  resultRelDesc,
+								  lfirst_int(l),
+								  NULL,
+								  estate->es_instrument);
+				resultRelInfo++;
+			}
+
+			estate->es_root_result_relations = resultRelInfos;
+			estate->es_num_root_result_relations = num_roots;
+
+			/* Simply lock the rest of them. */
 			foreach(l, plannedstmt->nonleafResultRelations)
 			{
-				Index		resultRelationIndex = lfirst_int(l);
-				Oid			resultRelationOid;
+				Index	resultRelIndex = lfirst_int(l);
 
-				resultRelationOid = getrelid(resultRelationIndex, rangeTable);
-				LockRelationOid(resultRelationOid, RowExclusiveLock);
+				/* We locked the roots above. */
+				if (!list_member_int(plannedstmt->rootResultRelations,
+									 resultRelIndex))
+					LockRelationOid(getrelid(resultRelIndex, rangeTable),
+									RowExclusiveLock);
 			}
 		}
 	}
@@ -883,6 +918,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_result_relations = NULL;
 		estate->es_num_result_relations = 0;
 		estate->es_result_relation_info = NULL;
+		estate->es_root_result_relations = NULL;
+		estate->es_num_root_result_relations = 0;
 	}
 
 	/*
@@ -1565,6 +1602,14 @@ ExecEndPlan(PlanState *planstate, EState *estate)
 		resultRelInfo++;
 	}
 
+	/* Close the root target relation(s). */
+	resultRelInfo = estate->es_root_result_relations;
+	for (i = estate->es_num_root_result_relations; i > 0; i--)
+	{
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		resultRelInfo++;
+	}
+
 	/*
 	 * likewise close any trigger target relations
 	 */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 71e3b8ec2d..654e777c67 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -62,6 +62,8 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
 					 EState *estate,
 					 bool canSetTag,
 					 TupleTableSlot **returning);
+static void fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
+static void fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo);
 
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1328,19 +1330,39 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 static void
 fireBSTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->rootResultRelInfo)
+	{
+		fireBSTriggersFor(node, node->rootResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireBSTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process BEFORE EACH STATEMENT triggers for one result relation
+ */
+static void
+fireBSTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
-			ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSInsertTriggers(node->ps.state, resultRelInfo);
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecBSUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
+				ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecBSDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1354,19 +1376,39 @@ fireBSTriggers(ModifyTableState *node)
 static void
 fireASTriggers(ModifyTableState *node)
 {
+	/* Fire for the root partitioned table, if any, and return. */
+	if (node->rootResultRelInfo)
+	{
+		fireASTriggersFor(node, node->rootResultRelInfo);
+		return;
+	}
+
+	/*
+	 * Fire for the main result relation.  In the partitioned table case,
+	 * the following happens to be the first leaf partition, which we don't
+	 * need to fire triggers for.
+	 */
+	fireASTriggersFor(node, node->resultRelInfo);
+}
+
+/*
+ * Process AFTER EACH STATEMENT triggers for one result relation
+ */
+static void
+fireASTriggersFor(ModifyTableState *node, ResultRelInfo *resultRelInfo)
+{
 	switch (node->operation)
 	{
 		case CMD_INSERT:
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
-				ExecASUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
-			ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
+				ExecASUpdateTriggers(node->ps.state, resultRelInfo);
+			ExecASInsertTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecASUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecASUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecASDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecASDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1652,6 +1694,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+	mtstate->rootResultRelInfo = estate->es_root_result_relations +
+												node->rootResultRelIndex;
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
 	mtstate->mt_nplans = nplans;
 	mtstate->mt_onconflict = node->onConflictAction;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 00a0fed23d..b58ea83065 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -91,6 +91,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(nonleafResultRelations);
+	COPY_NODE_FIELD(rootResultRelations);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -205,6 +206,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
+	COPY_SCALAR_FIELD(rootResultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 28cef85579..01cc4f3f47 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -253,6 +253,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
+	WRITE_NODE_FIELD(rootResultRelations);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -350,6 +351,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
+	WRITE_INT_FIELD(rootResultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
@@ -2145,6 +2147,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
+	WRITE_NODE_FIELD(rootResultRelations);
 	WRITE_NODE_FIELD(relationOids);
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_INT_FIELD(nParamExec);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a883220a49..f9a227e237 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1453,6 +1453,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(nonleafResultRelations);
+	READ_NODE_FIELD(rootResultRelations);
 	READ_NODE_FIELD(subplans);
 	READ_BITMAPSET_FIELD(rewindPlanIDs);
 	READ_NODE_FIELD(rowMarks);
@@ -1548,6 +1549,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
+	READ_INT_FIELD(rootResultRelIndex);
 	READ_NODE_FIELD(plans);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 95e6eb7d28..52daf43c81 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6437,6 +6437,7 @@ make_modifytable(PlannerInfo *root,
 	node->partitioned_rels = partitioned_rels;
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
+	node->rootResultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
 	if (!onconflict)
 	{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 649a233e11..c4a5651abd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -240,6 +240,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->nonleafResultRelations = NIL;
+	glob->rootResultRelations = NIL;
 	glob->relationOids = NIL;
 	glob->invalItems = NIL;
 	glob->nParamExec = 0;
@@ -408,6 +409,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->nonleafResultRelations == NIL);
+	Assert(glob->rootResultRelations == NIL);
 	top_plan = set_plan_references(root, top_plan);
 	/* ... and the subplans (both regular subplans and initplans) */
 	Assert(list_length(glob->subplans) == list_length(glob->subroots));
@@ -434,6 +436,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->rtable = glob->finalrtable;
 	result->resultRelations = glob->resultRelations;
 	result->nonleafResultRelations = glob->nonleafResultRelations;
+	result->rootResultRelations = glob->rootResultRelations;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1278371b65..edf00a6408 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -882,11 +882,22 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				/*
 				 * If the main target relation is a partitioned table, the
 				 * following list contains the RT indexes of partitioned child
-				 * relations, which are not included in the above list.
+				 * relations including the root, which are not included in the
+				 * above list.  We also keep RT indexes of the roots separately
+				 * to be identitied as such during executor initialization.
+				 * First, remember where this root will be in the global list.
 				 */
-				root->glob->nonleafResultRelations =
-					list_concat(root->glob->nonleafResultRelations,
-								list_copy(splan->partitioned_rels));
+				splan->rootResultRelIndex =
+								list_length(root->glob->rootResultRelations);
+				if (splan->partitioned_rels != NIL)
+				{
+					root->glob->nonleafResultRelations =
+						list_concat(root->glob->nonleafResultRelations,
+									list_copy(splan->partitioned_rels));
+					root->glob->rootResultRelations =
+								lappend_int(root->glob->rootResultRelations,
+									linitial_int(splan->partitioned_rels));
+				}
 			}
 			break;
 		case T_Append:
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4330a851c3..f289f3c3c2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -422,6 +422,16 @@ typedef struct EState
 	int			es_num_result_relations;		/* length of array */
 	ResultRelInfo *es_result_relation_info;		/* currently active array elt */
 
+	/*
+	 * Info about the target partitioned target table root(s) for
+	 * update/delete queries.  They required only to fire any per-statement
+	 * triggers defined on the table.  It exists separately from
+	 * es_result_relations, because partitioned tables don't appear in the
+	 * plan tree for the update/delete cases.
+	 */
+	ResultRelInfo *es_root_result_relations;	/* array of ResultRelInfos */
+	int			es_num_root_result_relations;	/* length of the array */
+
 	/* Stuff used for firing triggers: */
 	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
 	TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
@@ -914,6 +924,8 @@ typedef struct ModifyTableState
 	int			mt_nplans;		/* number of plans in the array */
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	ResultRelInfo *resultRelInfo;		/* per-subplan target relations */
+	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
+										 * table root) */
 	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cba915572e..164105a3a9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -65,9 +65,19 @@ typedef struct PlannedStmt
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
-	/* rtable indexes of non-leaf target relations for INSERT/UPDATE/DELETE */
+	/*
+	 * rtable indexes of non-leaf target relations for UPDATE/DELETE on
+	 * all the partitioned table mentioned in the query.
+	 */
 	List	   *nonleafResultRelations;
 
+	/*
+	 * rtable indexes of root target relations for UPDATE/DELETE; this list
+	 * maintains a subset of the RT indexes in nonleafResultRelations,
+	 * indicating the roots of the respective partition hierarchies.
+	 */
+	List	   *rootResultRelations;
+
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
 								 * that some could be NULL */
 
@@ -211,6 +221,7 @@ typedef struct ModifyTable
 	List	   *partitioned_rels;
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
+	int			rootResultRelIndex; /* index of the partitioned table root */
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 7a8e2fd2b8..adbd3dd556 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -108,6 +108,7 @@ typedef struct PlannerGlobal
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
 
 	List   *nonleafResultRelations; /* "flat" list of integer RT indexes */
+	List	   *rootResultRelations; /* "flat" list of integer RT indexes */
 
 	List	   *relationOids;	/* OIDs of relations the plan depends on */
 
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 4b0b3b7c42..10a301310b 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1787,3 +1787,84 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+create table parted2_stmt_trig (a int) partition by list (a);
+create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1);
+create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2);
+create or replace function trigger_notice() returns trigger as $$
+  begin
+    raise notice 'trigger on % % % for %', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+    if TG_LEVEL = 'ROW' then
+       return NEW;
+    end if;
+    return null;
+  end;
+  $$ language plpgsql;
+-- insert/update/delete statment-level triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+-- insert/update/delete row-level triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+-- insert/update/delete statement-level triggers on the parent
+create trigger trig_ins_before before insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+with ins (a) as (
+  insert into parted2_stmt_trig values (1), (2) returning a
+) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a;
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT for STATEMENT
+NOTICE:  trigger on parted2_stmt_trig BEFORE INSERT for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 BEFORE INSERT for ROW
+NOTICE:  trigger on parted_stmt_trig1 AFTER INSERT for ROW
+NOTICE:  trigger on parted2_stmt_trig AFTER INSERT for STATEMENT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT for STATEMENT
+     tableoid      | a 
+-------------------+---
+ parted_stmt_trig1 | 1
+ parted_stmt_trig2 | 2
+(2 rows)
+
+with upd as (
+  update parted2_stmt_trig set a = a
+) update parted_stmt_trig  set a = a;
+NOTICE:  trigger on parted_stmt_trig BEFORE UPDATE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 BEFORE UPDATE for ROW
+NOTICE:  trigger on parted2_stmt_trig BEFORE UPDATE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 AFTER UPDATE for ROW
+NOTICE:  trigger on parted_stmt_trig AFTER UPDATE for STATEMENT
+NOTICE:  trigger on parted2_stmt_trig AFTER UPDATE for STATEMENT
+delete from parted_stmt_trig;
+NOTICE:  trigger on parted_stmt_trig BEFORE DELETE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig AFTER DELETE for STATEMENT
+drop table parted_stmt_trig, parted2_stmt_trig;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 4473ce0518..84b5ada554 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1263,3 +1263,73 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+
+create table parted2_stmt_trig (a int) partition by list (a);
+create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1);
+create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2);
+
+create or replace function trigger_notice() returns trigger as $$
+  begin
+    raise notice 'trigger on % % % for %', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+    if TG_LEVEL = 'ROW' then
+       return NEW;
+    end if;
+    return null;
+  end;
+  $$ language plpgsql;
+
+-- insert/update/delete statment-level triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+-- insert/update/delete row-level triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+
+-- insert/update/delete statement-level triggers on the parent
+create trigger trig_ins_before before insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+with ins (a) as (
+  insert into parted2_stmt_trig values (1), (2) returning a
+) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a;
+
+with upd as (
+  update parted2_stmt_trig set a = a
+) update parted_stmt_trig  set a = a;
+
+delete from parted_stmt_trig;
+drop table parted_stmt_trig, parted2_stmt_trig;
-- 
2.11.0

#308Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Amit Langote (#307)
Re: Declarative partitioning - another take

On Fri, Apr 28, 2017 at 11:43 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

Updated patch attached.

Hi Amit,

I have applied given patch, could see below behaviour with statement
trigger.

When trying to delete value within partition range, triggers got fired
(with zero row affected) as expected, but if trying to delete value which
is outside of partition range (with zero row affected), No trigger fired.
is this expected??

Below are steps to reproduce.

CREATE TABLE trigger_test_table (a INT, b INT) PARTITION BY RANGE(a);
CREATE TABLE trigger_test_table1 PARTITION OF trigger_test_table FOR VALUES
FROM (0) to (6);
INSERT INTO trigger_test_table (a,b) SELECT i,i FROM generate_series(1,3)i;

CREATE TABLE trigger_test_statatics(TG_NAME varchar,TG_TABLE_NAME
varchar,TG_LEVEL varchar,TG_WHEN varchar, TG_OP varchar);

CREATE FUNCTION trigger_test_procedure() RETURNS TRIGGER AS $ttp$
BEGIN
INSERT INTO trigger_test_statatics SELECT
TG_NAME,TG_TABLE_NAME,TG_LEVEL,TG_WHEN,TG_OP;
RETURN OLD;
END;
$ttp$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_test11 AFTER DELETE ON trigger_test_table FOR EACH
STATEMENT EXECUTE PROCEDURE trigger_test_procedure();
CREATE TRIGGER trigger_test12 AFTER DELETE ON trigger_test_table1 FOR EACH
STATEMENT EXECUTE PROCEDURE trigger_test_procedure();

postgres=# DELETE FROM trigger_test_table WHERE a = 5;
DELETE 0
postgres=# SELECT * FROM trigger_test_statatics;
tg_name | tg_table_name | tg_level | tg_when | tg_op
----------------+--------------------+-----------+---------+--------
trigger_test11 | trigger_test_table | STATEMENT | AFTER | DELETE
(1 row)

TRUNCATE TABLE trigger_test_statatics;

--statement trigger NOT fired, when trying to delete data outside partition
range.
postgres=# DELETE FROM trigger_test_table WHERE a = 10;
DELETE 0
postgres=# SELECT * FROM trigger_test_statatics;
tg_name | tg_table_name | tg_level | tg_when | tg_op
---------+---------------+----------+---------+-------
(0 rows)

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#309Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rajkumar Raghuwanshi (#308)
Re: Declarative partitioning - another take

Hi Rajkumar,

On 2017/04/28 17:11, Rajkumar Raghuwanshi wrote:

On Fri, Apr 28, 2017 at 11:43 AM, Amit Langote <

Updated patch attached.

I have applied given patch, could see below behaviour with statement
trigger.

When trying to delete value within partition range, triggers got fired
(with zero row affected) as expected, but if trying to delete value which
is outside of partition range (with zero row affected), No trigger fired.
is this expected??

Good catch.

I'm afraid though that this is not a defect of this patch, but some
unrelated (maybe) bug, which affects not only the partitioned tables but
inheritance in general.

Problem is that the plan created is such that the executor has no
opportunity to fire the trigger in question, because the plan contains no
information about which table is affected by the statement. You can see
that with inheritance. See below:

create table foo ();
create table bar () inherits (foo);

create or replace function trig_notice() returns trigger as $$
begin raise notice 'trigger fired'; return null; end;
$$ language plpgsql;

create trigger foo_del_before before delete on foo
for each statement execute procedure trig_notice();

explain delete from foo where false;
QUERY PLAN
------------------------------------------
Result (cost=0.00..0.00 rows=0 width=0)
One-Time Filter: false
(2 rows)

-- no trigger fired
delete from foo where false;
DELETE 0

Trigger *is* fired though, if inheritance is not used.

explain delete from only foo where false;
QUERY PLAN
-------------------------------------------------
Delete on foo (cost=0.00..0.00 rows=0 width=0)
-> Result (cost=0.00..0.00 rows=0 width=6)
One-Time Filter: false
(3 rows)

delete from only foo where false;
NOTICE: trigger fired
DELETE 0

I'm not sure how to go about fixing this, if at all. Or to fix it at
least for partitioned tables. Would like to hear what others think.

Thanks,
Amit

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

#310Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: David Fetter (#306)
Re: Declarative partitioning - another take

On 2017/04/28 7:36, David Fetter wrote:

On Thu, Apr 27, 2017 at 10:30:54AM +0900, Amit Langote wrote:

On 2017/04/27 1:52, Robert Haas wrote:

On Tue, Apr 25, 2017 at 10:34 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

Did I notice correctly that there's no way to handle transition tables
for partitions in AFTER STATEMENT triggers?

Did you mean to ask about AFTER STATEMENT triggers defined on
"partitioned" tables? Specifying transition table for them is disallowed
at all.

ERROR: "p" is a partitioned table
DETAIL: Triggers on partitioned tables cannot have transition tables.

Triggers created on (leaf) partitions *do* allow specifying transition table.

Or are you asking something else altogether?

If not, I'm not suggesting that this be added at this late date, but
we might want to document that.

I don't see mentioned in the documentation that such triggers cannot be
defined on partitioned tables. Is that what you are saying should be
documented?

Thanks,
Amit

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

#311Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#307)
Re: Declarative partitioning - another take

On Fri, Apr 28, 2017 at 2:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

It seems to me that there is no difference in behavior between
inheritance-based and declarative partitioning as far as statement-level
triggers are concerned (at least currently). In both the cases, we fire
statement-level triggers only for the table specified in the command.

OK.

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

It seems better not to create any ResultRelInfos that we don't
actually need, so +1 for such a revision to the patch.

OK, done. It took a bit more work than I thought.

So, this seems weird, because rootResultRelIndex is initialized even
when splan->partitioned_rels == NIL, but isn't actually valid in that
case. ExecInitModifyTable seems to think it's always valid, though.

I think the way that you've refactored fireBSTriggers and
fireASTriggers is a bit confusing. Instead of splitting out a
separate function, how about just having the existing function begin
with if (node->rootResultRelInfo) resultRelInfo =
node->rootResultRelInfo; else resultRelInfo = node->resultRelInfo; ?
I think the way you've coded it is a holdover from the earlier design
where you were going to call it multiple times, but now that's not
needed.

Looks OK, otherwise.

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

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

#312David Fetter
david@fetter.org
In reply to: Amit Langote (#310)
Re: Declarative partitioning - another take

On Fri, Apr 28, 2017 at 06:29:48PM +0900, Amit Langote wrote:

On 2017/04/28 7:36, David Fetter wrote:

On Thu, Apr 27, 2017 at 10:30:54AM +0900, Amit Langote wrote:

On 2017/04/27 1:52, Robert Haas wrote:

On Tue, Apr 25, 2017 at 10:34 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

FWIW, I too prefer the latter, that is, fire only the parent's triggers.
In that case, applying only the patch 0001 will do.

Do we need to update the documentation?

Yes, I think we should. How about as in the attached?

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

Did I notice correctly that there's no way to handle transition tables
for partitions in AFTER STATEMENT triggers?

Did you mean to ask about AFTER STATEMENT triggers defined on
"partitioned" tables? Specifying transition table for them is disallowed
at all.

ERROR: "p" is a partitioned table
DETAIL: Triggers on partitioned tables cannot have transition tables.

OK, I suppose. It wasn't clear from the documentation.

Triggers created on (leaf) partitions *do* allow specifying transition table.

That includes the upcoming "default" tables, I presume.

Or are you asking something else altogether?

I was just fuzzy on the interactions among these features.

If not, I'm not suggesting that this be added at this late date, but
we might want to document that.

I don't see mentioned in the documentation that such triggers cannot be
defined on partitioned tables. Is that what you are saying should be
documented?

Yes, but I bias toward documenting a lot, and this restriction could
go away in some future version, which would make things more confusing
in the long run. I'm picturing a conversation in 2020 that goes
something like this:

"On 10, you could have AFTER STATEMENT triggers on tables, foreigh
tables, and leaf partition tables which referenced transition tables,
but not on DEFAULT partitions. On 11, you could on DEFAULT partition
tables. From 12 onward, you can have transition tables on any
relation."

Kevin? Thomas?

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

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

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

#313Noah Misch
noah@leadboat.com
In reply to: Amit Langote (#297)
Re: Declarative partitioning - another take

On Mon, Apr 24, 2017 at 07:43:29PM +0900, Amit Langote wrote:

On 2017/04/21 17:00, Rajkumar Raghuwanshi wrote:

I am able to create statement triggers at root partition, but these
triggers, not getting fired on updating partition.

It would be great if you could check if the patches fix the issues.

Also, adding this to the PostgreSQL 10 open items list.

[Action required within three days. This is a generic notification.]

The above-described topic is currently a PostgreSQL 10 open item. Robert,
since you committed the patch believed to have created it, you own this open
item. If some other commit is more relevant or if this does not belong as a
v10 open item, please let us know. Otherwise, please observe the policy on
open item ownership[1]/messages/by-id/20170404140717.GA2675809@tornado.leadboat.com and send a status update within three calendar days of
this message. Include a date for your subsequent status update. Testers may
discover new open items at any time, and I want to plan to get them all fixed
well in advance of shipping v10. Consequently, I will appreciate your efforts
toward speedy resolution. Thanks.

[1]: /messages/by-id/20170404140717.GA2675809@tornado.leadboat.com

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

#314Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#311)
1 attachment(s)
Re: Declarative partitioning - another take

Thanks for the review.

On 2017/04/29 1:25, Robert Haas wrote:

On Fri, Apr 28, 2017 at 2:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

By the way, code changes I made in the attached are such that a subsequent
patch could implement firing statement-level triggers of all the tables in
a partition hierarchy, which it seems we don't want to do. Should then
the code be changed to not create ResultRelInfos of all the tables but
only the root table (the one mentioned in the command)? You will see that
the patch adds fields named es_nonleaf_result_relations and
es_num_nonleaf_result_relations, whereas just es_root_result_relation
would perhaps do, for example.

It seems better not to create any ResultRelInfos that we don't
actually need, so +1 for such a revision to the patch.

OK, done. It took a bit more work than I thought.

So, this seems weird, because rootResultRelIndex is initialized even
when splan->partitioned_rels == NIL, but isn't actually valid in that
case. ExecInitModifyTable seems to think it's always valid, though.

OK, rootResultRelIndex is now set to a value >= 0 only when the node
modifies a partitioned table. And then ExecInitModifyTable() checks if
the index is valid before initializing the root table info.

I think the way that you've refactored fireBSTriggers and
fireASTriggers is a bit confusing. Instead of splitting out a
separate function, how about just having the existing function begin
with if (node->rootResultRelInfo) resultRelInfo =
node->rootResultRelInfo; else resultRelInfo = node->resultRelInfo; ?
I think the way you've coded it is a holdover from the earlier design
where you were going to call it multiple times, but now that's not
needed.

OK, done that way.

Looks OK, otherwise.

Attached updated patch.

Thanks,
Amit

Attachments:

0001-Fire-per-statement-triggers-of-partitioned-tables.patchtext/x-diff; name=0001-Fire-per-statement-triggers-of-partitioned-tables.patchDownload
From 53c964f9f1fd3e27824080b0e9e5b672fa53cdf0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH] Fire per-statement triggers of partitioned tables

Current implementation completely misses them, because it assumes
that no ResultRelInfos need to be created for partitioned tables,
whereas they *are* needed to fire the per-statment triggers, which
we *do* allow to be defined on the partitioned tables.

Reported By: Rajkumar Raghuwanshi
Patch by: Amit Langote
Report: https://www.postgresql.org/message-id/CAKcux6%3DwYospCRY2J4XEFuVy0L41S%3Dfic7rmkbsU-GXhhSbmBg%40mail.gmail.com
---
 doc/src/sgml/trigger.sgml               | 26 +++++++----
 src/backend/executor/execMain.c         | 55 ++++++++++++++++++++--
 src/backend/executor/nodeModifyTable.c  | 42 +++++++++++++----
 src/backend/nodes/copyfuncs.c           |  2 +
 src/backend/nodes/outfuncs.c            |  3 ++
 src/backend/nodes/readfuncs.c           |  2 +
 src/backend/optimizer/plan/createplan.c |  1 +
 src/backend/optimizer/plan/planner.c    |  3 ++
 src/backend/optimizer/plan/setrefs.c    | 19 ++++++--
 src/include/nodes/execnodes.h           | 12 +++++
 src/include/nodes/plannodes.h           | 13 +++++-
 src/include/nodes/relation.h            |  1 +
 src/test/regress/expected/triggers.out  | 81 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql       | 70 ++++++++++++++++++++++++++++
 14 files changed, 303 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 2a718d7f47..5771bd5fdc 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -33,7 +33,8 @@
    <para>
     A trigger is a specification that the database should automatically
     execute a particular function whenever a certain type of operation is
-    performed.  Triggers can be attached to tables, views, and foreign tables.
+    performed.  Triggers can be attached to tables (partitioned or not),
+    views, and foreign tables.
   </para>
 
   <para>
@@ -111,14 +112,21 @@
     Statement-level <literal>BEFORE</> triggers naturally fire before the
     statement starts to do anything, while statement-level <literal>AFTER</>
     triggers fire at the very end of the statement.  These types of
-    triggers may be defined on tables or views.  Row-level <literal>BEFORE</>
-    triggers fire immediately before a particular row is operated on,
-    while row-level <literal>AFTER</> triggers fire at the end of the
-    statement (but before any statement-level <literal>AFTER</> triggers).
-    These types of triggers may only be defined on tables and foreign tables.
-    Row-level <literal>INSTEAD OF</> triggers may only be defined on views,
-    and fire immediately as each row in the view is identified as needing to
-    be operated on.
+    triggers may be defined on tables, views, or foreign tables.  Row-level
+    <literal>BEFORE</> triggers fire immediately before a particular row is
+    operated on, while row-level <literal>AFTER</> triggers fire at the end of
+    the statement (but before any statement-level <literal>AFTER</> triggers).
+    These types of triggers may only be defined on non-partitioned tables and
+    foreign tables.  Row-level <literal>INSTEAD OF</> triggers may only be
+    defined on views, and fire immediately as each row in the view is
+    identified as needing to be operated on.
+   </para>
+
+   <para>
+    A statement-level trigger defined on partitioned tables is fired only
+    once for the table itself, not once for every table in the partitioning
+    hierarchy.  However, row-level triggers of any affected leaf partitions
+    will be fired.
    </para>
 
    <para>
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5c12fb457d..cdb1a6a5f5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -861,17 +861,52 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 		/*
 		 * In the partitioned result relation case, lock the non-leaf result
-		 * relations too.  We don't however need ResultRelInfos for them.
+		 * relations too.  A subset of these are the roots of respective
+		 * partitioned tables, for which we also allocate ResulRelInfos.
 		 */
+		estate->es_root_result_relations = NULL;
+		estate->es_num_root_result_relations = 0;
 		if (plannedstmt->nonleafResultRelations)
 		{
+			int		num_roots = list_length(plannedstmt->rootResultRelations);
+
+			/*
+			 * Firstly, build ResultRelInfos for all the partitioned table
+			 * roots, because we will need them to fire the statement-level
+			 * triggers, if any.
+			 */
+			resultRelInfos = (ResultRelInfo *)
+									palloc(num_roots * sizeof(ResultRelInfo));
+			resultRelInfo = resultRelInfos;
+			foreach(l, plannedstmt->rootResultRelations)
+			{
+				Index		resultRelIndex = lfirst_int(l);
+				Oid			resultRelOid;
+				Relation	resultRelDesc;
+
+				resultRelOid = getrelid(resultRelIndex, rangeTable);
+				resultRelDesc = heap_open(resultRelOid, RowExclusiveLock);
+				InitResultRelInfo(resultRelInfo,
+								  resultRelDesc,
+								  lfirst_int(l),
+								  NULL,
+								  estate->es_instrument);
+				resultRelInfo++;
+			}
+
+			estate->es_root_result_relations = resultRelInfos;
+			estate->es_num_root_result_relations = num_roots;
+
+			/* Simply lock the rest of them. */
 			foreach(l, plannedstmt->nonleafResultRelations)
 			{
-				Index		resultRelationIndex = lfirst_int(l);
-				Oid			resultRelationOid;
+				Index	resultRelIndex = lfirst_int(l);
 
-				resultRelationOid = getrelid(resultRelationIndex, rangeTable);
-				LockRelationOid(resultRelationOid, RowExclusiveLock);
+				/* We locked the roots above. */
+				if (!list_member_int(plannedstmt->rootResultRelations,
+									 resultRelIndex))
+					LockRelationOid(getrelid(resultRelIndex, rangeTable),
+									RowExclusiveLock);
 			}
 		}
 	}
@@ -883,6 +918,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		estate->es_result_relations = NULL;
 		estate->es_num_result_relations = 0;
 		estate->es_result_relation_info = NULL;
+		estate->es_root_result_relations = NULL;
+		estate->es_num_root_result_relations = 0;
 	}
 
 	/*
@@ -1565,6 +1602,14 @@ ExecEndPlan(PlanState *planstate, EState *estate)
 		resultRelInfo++;
 	}
 
+	/* Close the root target relation(s). */
+	resultRelInfo = estate->es_root_result_relations;
+	for (i = estate->es_num_root_result_relations; i > 0; i--)
+	{
+		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+		resultRelInfo++;
+	}
+
 	/*
 	 * likewise close any trigger target relations
 	 */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 71e3b8ec2d..652cd97599 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1328,19 +1328,29 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 static void
 fireBSTriggers(ModifyTableState *node)
 {
+	ResultRelInfo	*resultRelInfo = node->resultRelInfo;
+
+	/*
+	 * If the node modifies a partitioned table, we must fire its triggers.
+	 * Note that in that case, node->resultRelInfo points to the first leaf
+	 * partition, not the root table.
+	 */
+	if (node->rootResultRelInfo != NULL)
+		resultRelInfo = node->rootResultRelInfo;
+
 	switch (node->operation)
 	{
 		case CMD_INSERT:
-			ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSInsertTriggers(node->ps.state, resultRelInfo);
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
 				ExecBSUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
+									 resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecBSDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1354,19 +1364,29 @@ fireBSTriggers(ModifyTableState *node)
 static void
 fireASTriggers(ModifyTableState *node)
 {
+	ResultRelInfo	*resultRelInfo = node->resultRelInfo;
+
+	/*
+	 * If the node modifies a partitioned table, we must fire its triggers.
+	 * Note that in that case, node->resultRelInfo points to the first leaf
+	 * partition, not the root table.
+	 */
+	if (node->rootResultRelInfo != NULL)
+		resultRelInfo = node->rootResultRelInfo;
+
 	switch (node->operation)
 	{
 		case CMD_INSERT:
 			if (node->mt_onconflict == ONCONFLICT_UPDATE)
 				ExecASUpdateTriggers(node->ps.state,
-									 node->resultRelInfo);
-			ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
+									 resultRelInfo);
+			ExecASInsertTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_UPDATE:
-			ExecASUpdateTriggers(node->ps.state, node->resultRelInfo);
+			ExecASUpdateTriggers(node->ps.state, resultRelInfo);
 			break;
 		case CMD_DELETE:
-			ExecASDeleteTriggers(node->ps.state, node->resultRelInfo);
+			ExecASDeleteTriggers(node->ps.state, resultRelInfo);
 			break;
 		default:
 			elog(ERROR, "unknown operation");
@@ -1652,6 +1672,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+
+	/* If modifying a partitioned table, initialize the root table info */
+	if (node->rootResultRelIndex >= 0)
+		mtstate->rootResultRelInfo = estate->es_root_result_relations +
+												node->rootResultRelIndex;
+
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
 	mtstate->mt_nplans = nplans;
 	mtstate->mt_onconflict = node->onConflictAction;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8fb872d288..35a237a000 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -91,6 +91,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(nonleafResultRelations);
+	COPY_NODE_FIELD(rootResultRelations);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -205,6 +206,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(partitioned_rels);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
+	COPY_SCALAR_FIELD(rootResultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 05a78b32b7..98f67681a7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -253,6 +253,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
+	WRITE_NODE_FIELD(rootResultRelations);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -350,6 +351,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
+	WRITE_INT_FIELD(rootResultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
@@ -2145,6 +2147,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(nonleafResultRelations);
+	WRITE_NODE_FIELD(rootResultRelations);
 	WRITE_NODE_FIELD(relationOids);
 	WRITE_NODE_FIELD(invalItems);
 	WRITE_INT_FIELD(nParamExec);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a883220a49..f9a227e237 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1453,6 +1453,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(nonleafResultRelations);
+	READ_NODE_FIELD(rootResultRelations);
 	READ_NODE_FIELD(subplans);
 	READ_BITMAPSET_FIELD(rewindPlanIDs);
 	READ_NODE_FIELD(rowMarks);
@@ -1548,6 +1549,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(partitioned_rels);
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
+	READ_INT_FIELD(rootResultRelIndex);
 	READ_NODE_FIELD(plans);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 95e6eb7d28..52daf43c81 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6437,6 +6437,7 @@ make_modifytable(PlannerInfo *root,
 	node->partitioned_rels = partitioned_rels;
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
+	node->rootResultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->plans = subplans;
 	if (!onconflict)
 	{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 649a233e11..c4a5651abd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -240,6 +240,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->finalrowmarks = NIL;
 	glob->resultRelations = NIL;
 	glob->nonleafResultRelations = NIL;
+	glob->rootResultRelations = NIL;
 	glob->relationOids = NIL;
 	glob->invalItems = NIL;
 	glob->nParamExec = 0;
@@ -408,6 +409,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	Assert(glob->finalrowmarks == NIL);
 	Assert(glob->resultRelations == NIL);
 	Assert(glob->nonleafResultRelations == NIL);
+	Assert(glob->rootResultRelations == NIL);
 	top_plan = set_plan_references(root, top_plan);
 	/* ... and the subplans (both regular subplans and initplans) */
 	Assert(list_length(glob->subplans) == list_length(glob->subroots));
@@ -434,6 +436,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->rtable = glob->finalrtable;
 	result->resultRelations = glob->resultRelations;
 	result->nonleafResultRelations = glob->nonleafResultRelations;
+	result->rootResultRelations = glob->rootResultRelations;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1278371b65..c192dc4f70 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -882,11 +882,22 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				/*
 				 * If the main target relation is a partitioned table, the
 				 * following list contains the RT indexes of partitioned child
-				 * relations, which are not included in the above list.
+				 * relations including the root, which are not included in the
+				 * above list.  We also keep RT indexes of the roots separately
+				 * to be identitied as such during the executor initialization.
 				 */
-				root->glob->nonleafResultRelations =
-					list_concat(root->glob->nonleafResultRelations,
-								list_copy(splan->partitioned_rels));
+				if (splan->partitioned_rels != NIL)
+				{
+					root->glob->nonleafResultRelations =
+						list_concat(root->glob->nonleafResultRelations,
+									list_copy(splan->partitioned_rels));
+					/* Remember where this root will be in the global list. */
+					splan->rootResultRelIndex =
+								list_length(root->glob->rootResultRelations);
+					root->glob->rootResultRelations =
+								lappend_int(root->glob->rootResultRelations,
+									linitial_int(splan->partitioned_rels));
+				}
 			}
 			break;
 		case T_Append:
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4330a851c3..f289f3c3c2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -422,6 +422,16 @@ typedef struct EState
 	int			es_num_result_relations;		/* length of array */
 	ResultRelInfo *es_result_relation_info;		/* currently active array elt */
 
+	/*
+	 * Info about the target partitioned target table root(s) for
+	 * update/delete queries.  They required only to fire any per-statement
+	 * triggers defined on the table.  It exists separately from
+	 * es_result_relations, because partitioned tables don't appear in the
+	 * plan tree for the update/delete cases.
+	 */
+	ResultRelInfo *es_root_result_relations;	/* array of ResultRelInfos */
+	int			es_num_root_result_relations;	/* length of the array */
+
 	/* Stuff used for firing triggers: */
 	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
 	TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
@@ -914,6 +924,8 @@ typedef struct ModifyTableState
 	int			mt_nplans;		/* number of plans in the array */
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	ResultRelInfo *resultRelInfo;		/* per-subplan target relations */
+	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
+										 * table root) */
 	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cba915572e..164105a3a9 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -65,9 +65,19 @@ typedef struct PlannedStmt
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
-	/* rtable indexes of non-leaf target relations for INSERT/UPDATE/DELETE */
+	/*
+	 * rtable indexes of non-leaf target relations for UPDATE/DELETE on
+	 * all the partitioned table mentioned in the query.
+	 */
 	List	   *nonleafResultRelations;
 
+	/*
+	 * rtable indexes of root target relations for UPDATE/DELETE; this list
+	 * maintains a subset of the RT indexes in nonleafResultRelations,
+	 * indicating the roots of the respective partition hierarchies.
+	 */
+	List	   *rootResultRelations;
+
 	List	   *subplans;		/* Plan trees for SubPlan expressions; note
 								 * that some could be NULL */
 
@@ -211,6 +221,7 @@ typedef struct ModifyTable
 	List	   *partitioned_rels;
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
+	int			rootResultRelIndex; /* index of the partitioned table root */
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 7a8e2fd2b8..adbd3dd556 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -108,6 +108,7 @@ typedef struct PlannerGlobal
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
 
 	List   *nonleafResultRelations; /* "flat" list of integer RT indexes */
+	List	   *rootResultRelations; /* "flat" list of integer RT indexes */
 
 	List	   *relationOids;	/* OIDs of relations the plan depends on */
 
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 4b0b3b7c42..10a301310b 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1787,3 +1787,84 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+create table parted2_stmt_trig (a int) partition by list (a);
+create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1);
+create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2);
+create or replace function trigger_notice() returns trigger as $$
+  begin
+    raise notice 'trigger on % % % for %', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+    if TG_LEVEL = 'ROW' then
+       return NEW;
+    end if;
+    return null;
+  end;
+  $$ language plpgsql;
+-- insert/update/delete statment-level triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+-- insert/update/delete row-level triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+-- insert/update/delete statement-level triggers on the parent
+create trigger trig_ins_before before insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+with ins (a) as (
+  insert into parted2_stmt_trig values (1), (2) returning a
+) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a;
+NOTICE:  trigger on parted_stmt_trig BEFORE INSERT for STATEMENT
+NOTICE:  trigger on parted2_stmt_trig BEFORE INSERT for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 BEFORE INSERT for ROW
+NOTICE:  trigger on parted_stmt_trig1 AFTER INSERT for ROW
+NOTICE:  trigger on parted2_stmt_trig AFTER INSERT for STATEMENT
+NOTICE:  trigger on parted_stmt_trig AFTER INSERT for STATEMENT
+     tableoid      | a 
+-------------------+---
+ parted_stmt_trig1 | 1
+ parted_stmt_trig2 | 2
+(2 rows)
+
+with upd as (
+  update parted2_stmt_trig set a = a
+) update parted_stmt_trig  set a = a;
+NOTICE:  trigger on parted_stmt_trig BEFORE UPDATE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 BEFORE UPDATE for ROW
+NOTICE:  trigger on parted2_stmt_trig BEFORE UPDATE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig1 AFTER UPDATE for ROW
+NOTICE:  trigger on parted_stmt_trig AFTER UPDATE for STATEMENT
+NOTICE:  trigger on parted2_stmt_trig AFTER UPDATE for STATEMENT
+delete from parted_stmt_trig;
+NOTICE:  trigger on parted_stmt_trig BEFORE DELETE for STATEMENT
+NOTICE:  trigger on parted_stmt_trig AFTER DELETE for STATEMENT
+drop table parted_stmt_trig, parted2_stmt_trig;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 4473ce0518..84b5ada554 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1263,3 +1263,73 @@ create trigger my_trigger after update on my_table_42 referencing old table as o
 drop trigger my_trigger on my_table_42;
 drop table my_table_42;
 drop table my_table;
+
+--
+-- Verify that per-statement triggers are fired for partitioned tables
+--
+create table parted_stmt_trig (a int) partition by list (a);
+create table parted_stmt_trig1 partition of parted_stmt_trig for values in (1);
+create table parted_stmt_trig2 partition of parted_stmt_trig for values in (2);
+
+create table parted2_stmt_trig (a int) partition by list (a);
+create table parted2_stmt_trig1 partition of parted2_stmt_trig for values in (1);
+create table parted2_stmt_trig2 partition of parted2_stmt_trig for values in (2);
+
+create or replace function trigger_notice() returns trigger as $$
+  begin
+    raise notice 'trigger on % % % for %', TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+    if TG_LEVEL = 'ROW' then
+       return NEW;
+    end if;
+    return null;
+  end;
+  $$ language plpgsql;
+
+-- insert/update/delete statment-level triggers on the parent
+create trigger trig_ins_before before insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+-- insert/update/delete row-level triggers on the first partition
+create trigger trig_ins_before before insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted_stmt_trig1
+  for each row execute procedure trigger_notice();
+
+-- insert/update/delete statement-level triggers on the parent
+create trigger trig_ins_before before insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_ins_after after insert on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_before before update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_upd_after after update on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_before before delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+create trigger trig_del_after after delete on parted2_stmt_trig
+  for each statement execute procedure trigger_notice();
+
+with ins (a) as (
+  insert into parted2_stmt_trig values (1), (2) returning a
+) insert into parted_stmt_trig select a from ins returning tableoid::regclass, a;
+
+with upd as (
+  update parted2_stmt_trig set a = a
+) update parted_stmt_trig  set a = a;
+
+delete from parted_stmt_trig;
+drop table parted_stmt_trig, parted2_stmt_trig;
-- 
2.11.0

#315Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: David Fetter (#312)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/04/29 6:56, David Fetter wrote:

On Fri, Apr 28, 2017 at 06:29:48PM +0900, Amit Langote wrote:

On 2017/04/28 7:36, David Fetter wrote:

Did I notice correctly that there's no way to handle transition tables
for partitions in AFTER STATEMENT triggers?

Did you mean to ask about AFTER STATEMENT triggers defined on
"partitioned" tables? Specifying transition table for them is disallowed
at all.

ERROR: "p" is a partitioned table
DETAIL: Triggers on partitioned tables cannot have transition tables.

OK, I suppose. It wasn't clear from the documentation.

Triggers created on (leaf) partitions *do* allow specifying transition table.

That includes the upcoming "default" tables, I presume.

If a "default" table is also a "leaf" table, then yes. A default
table/partition can also be itself a partitioned table, in which case, it
won't allow triggers that require transition tables. AFAICS, it's the
table's being partitioned that stops it from supporting transition tables,
not whether it is a "default" partition or not.

Or are you asking something else altogether?

I was just fuzzy on the interactions among these features.

If not, I'm not suggesting that this be added at this late date, but
we might want to document that.

I don't see mentioned in the documentation that such triggers cannot be
defined on partitioned tables. Is that what you are saying should be
documented?

Yes, but I bias toward documenting a lot, and this restriction could
go away in some future version, which would make things more confusing
in the long run.

Yeah, it would be a good idea to document this.

I'm picturing a conversation in 2020 that goes
something like this:

"On 10, you could have AFTER STATEMENT triggers on tables, foreigh
tables, and leaf partition tables which referenced transition tables,
but not on DEFAULT partitions. On 11, you could on DEFAULT partition
tables. From 12 onward, you can have transition tables on any
relation."

What we could document now is that partitioned tables don't allow
specifying triggers that reference transition tables. Although, I am
wondering where this note really belongs - the partitioning chapter, the
triggers chapter or the CREATE TRIGGER reference page? Maybe, Kevin and
Thomas have something to say about that. If it turns out that the
partitioning chapter is a good place, here is a documentation patch.

Thanks,
Amit

Attachments:

0001-Document-transition-table-trigger-limitation-of-part.patchtext/x-diff; name=0001-Document-transition-table-trigger-limitation-of-part.patchDownload
From 289b4d906abb50451392d0efe13926f710952ca0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 1 May 2017 13:46:58 +0900
Subject: [PATCH] Document transition table trigger limitation of partitioned
 tables

---
 doc/src/sgml/ddl.sgml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 84c4f20990..5f5a956d41 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3293,7 +3293,9 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
      <listitem>
       <para>
        Row triggers, if necessary, must be defined on individual partitions,
-       not the partitioned table.
+       not the partitioned tables.  Also, triggers that require accessing
+       transition tables are not allowed to be defined on the partitioned
+       tables.
       </para>
      </listitem>
     </itemizedlist>
-- 
2.11.0

#316Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#314)
Re: Declarative partitioning - another take

On Mon, May 1, 2017 at 12:18 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached updated patch.

Committed, except for this bit:

+    A statement-level trigger defined on partitioned tables is fired only
+    once for the table itself, not once for every table in the partitioning
+    hierarchy.  However, row-level triggers of any affected leaf partitions
+    will be fired.

The first sentence there has a number of issues. Grammatically, there
is an agreement problem: trigger is singular, but partitioned table is
plural, and one trigger isn't defined across multiple tables. It
would have to say something like "A statement-level trigger defined on
a partitioned table". But even with that correction, it's not really
saying what you want it to say. Nobody would expect that the same
statement-level trigger would be fired multiple times. The issue is
whether statement-level triggers on the partitions themselves will be
fired, not the statement-level trigger on the partitioned table.
Also, if this applies to inheritance as well as partitioning, then why
mention only partitioning? I think we might want to document
something more here, but not like this.

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

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

#317Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#316)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/05/01 21:30, Robert Haas wrote:

On Mon, May 1, 2017 at 12:18 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached updated patch.

Committed, except for this bit:

Thanks.

+    A statement-level trigger defined on partitioned tables is fired only
+    once for the table itself, not once for every table in the partitioning
+    hierarchy.  However, row-level triggers of any affected leaf partitions
+    will be fired.

The first sentence there has a number of issues. Grammatically, there
is an agreement problem: trigger is singular, but partitioned table is
plural, and one trigger isn't defined across multiple tables. It
would have to say something like "A statement-level trigger defined on
a partitioned table". But even with that correction, it's not really
saying what you want it to say. Nobody would expect that the same
statement-level trigger would be fired multiple times. The issue is
whether statement-level triggers on the partitions themselves will be
fired, not the statement-level trigger on the partitioned table.
Also, if this applies to inheritance as well as partitioning, then why
mention only partitioning? I think we might want to document
something more here, but not like this.

You're right. I agree that whatever text we add here should be pointing
out that statement-level triggers of affected child tables are not fired,
when root parent is specified in the command.

Since there was least some talk of changing that behavior for regular
inheritance so that statement triggers of any affected children are fired
[1]: /messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com
inheritance and partitioning. But since nothing has happened in that
regard, we might as well.

How about the attached?

Thanks,
Amit

[1]: /messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com
/messages/by-id/cd282adde5b70b20c57f53bb9ab75e27@biglumber.com

Attachments:

0001-Clarify-statement-trigger-behavior-with-inheritance.patchtext/x-diff; name=0001-Clarify-statement-trigger-behavior-with-inheritance.patchDownload
From d2d27ca458fbd341efd5ae231f977385a5e3c951 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH] Clarify statement trigger behavior with inheritance

---
 doc/src/sgml/trigger.sgml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 6f8416dda7..ef41085744 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -123,6 +123,14 @@
    </para>
 
    <para>
+    An Operation that targets the root table in a inheritance or partitioning
+    hierarchy does not cause the statement-level triggers of affected child
+    tables to be fired; only the root table's statement-level triggers are
+    fired.  However, row-level triggers of any affected child tables will be
+    fired.
+   </para>
+
+   <para>
     If an <command>INSERT</command> contains an <literal>ON CONFLICT
     DO UPDATE</> clause, it is possible that the effects of all
     row-level <literal>BEFORE</> <command>INSERT</command> triggers
-- 
2.11.0

#318Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#317)
Re: Declarative partitioning - another take

On Tue, May 2, 2017 at 3:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

You're right. I agree that whatever text we add here should be pointing
out that statement-level triggers of affected child tables are not fired,
when root parent is specified in the command.

Since there was least some talk of changing that behavior for regular
inheritance so that statement triggers of any affected children are fired
[1], I thought we shouldn't say something general that applies to both
inheritance and partitioning. But since nothing has happened in that
regard, we might as well.

How about the attached?

Looks better, but I think we should say "statement" instead of
"operation" for consistency with the previous paragraph, and it
certainly shouldn't be capitalized.

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

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

#319Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#318)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/05/03 2:48, Robert Haas wrote:

On Tue, May 2, 2017 at 3:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

You're right. I agree that whatever text we add here should be pointing
out that statement-level triggers of affected child tables are not fired,
when root parent is specified in the command.

Since there was least some talk of changing that behavior for regular
inheritance so that statement triggers of any affected children are fired
[1], I thought we shouldn't say something general that applies to both
inheritance and partitioning. But since nothing has happened in that
regard, we might as well.

How about the attached?

Looks better, but I think we should say "statement" instead of
"operation" for consistency with the previous paragraph, and it
certainly shouldn't be capitalized.

Agreed, done. Attached updated patch.

Thanks,
Amit

Attachments:

0001-Clarify-statement-trigger-behavior-with-inheritance.patchtext/x-diff; name=0001-Clarify-statement-trigger-behavior-with-inheritance.patchDownload
From 1d7e383c6d89ebabacc7aa3f7d9987779daaa4fb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH] Clarify statement trigger behavior with inheritance

---
 doc/src/sgml/trigger.sgml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 6f8416dda7..89e8c01a71 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -123,6 +123,14 @@
    </para>
 
    <para>
+    A statement that targets the root table in a inheritance or partitioning
+    hierarchy does not cause the statement-level triggers of affected child
+    tables to be fired; only the root table's statement-level triggers are
+    fired.  However, row-level triggers of any affected child tables will be
+    fired.
+   </para>
+
+   <para>
     If an <command>INSERT</command> contains an <literal>ON CONFLICT
     DO UPDATE</> clause, it is possible that the effects of all
     row-level <literal>BEFORE</> <command>INSERT</command> triggers
-- 
2.11.0

#320Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Amit Langote (#319)
Re: Declarative partitioning - another take

On Mon, May 8, 2017 at 12:47 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/05/03 2:48, Robert Haas wrote:

On Tue, May 2, 2017 at 3:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

You're right. I agree that whatever text we add here should be pointing
out that statement-level triggers of affected child tables are not fired,
when root parent is specified in the command.

Since there was least some talk of changing that behavior for regular
inheritance so that statement triggers of any affected children are fired
[1], I thought we shouldn't say something general that applies to both
inheritance and partitioning. But since nothing has happened in that
regard, we might as well.

How about the attached?

Looks better, but I think we should say "statement" instead of
"operation" for consistency with the previous paragraph, and it
certainly shouldn't be capitalized.

Agreed, done. Attached updated patch.

    <para>
+    A statement that targets the root table in a inheritance or partitioning
+    hierarchy does not cause the statement-level triggers of affected child
+    tables to be fired; only the root table's statement-level triggers are
+    fired.  However, row-level triggers of any affected child tables will be
+    fired.
+   </para>
+
+   <para>

Why talk specifically about the "root" table? Wouldn't we describe
the situation more generally if we said [a,the] "parent"?

--
Thomas Munro
http://www.enterprisedb.com

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

#321Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Thomas Munro (#320)
1 attachment(s)
Re: Declarative partitioning - another take

On 2017/05/08 10:22, Thomas Munro wrote:

On Mon, May 8, 2017 at 12:47 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/05/03 2:48, Robert Haas wrote:

On Tue, May 2, 2017 at 3:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

You're right. I agree that whatever text we add here should be pointing
out that statement-level triggers of affected child tables are not fired,
when root parent is specified in the command.

Since there was least some talk of changing that behavior for regular
inheritance so that statement triggers of any affected children are fired
[1], I thought we shouldn't say something general that applies to both
inheritance and partitioning. But since nothing has happened in that
regard, we might as well.

How about the attached?

Looks better, but I think we should say "statement" instead of
"operation" for consistency with the previous paragraph, and it
certainly shouldn't be capitalized.

Agreed, done. Attached updated patch.

<para>
+    A statement that targets the root table in a inheritance or partitioning
+    hierarchy does not cause the statement-level triggers of affected child
+    tables to be fired; only the root table's statement-level triggers are
+    fired.  However, row-level triggers of any affected child tables will be
+    fired.
+   </para>
+
+   <para>

Why talk specifically about the "root" table? Wouldn't we describe
the situation more generally if we said [a,the] "parent"?

I think that makes sense. Modified it to read: "A statement that targets
a parent table in a inheritance or partitioning hierarchy..." in the
attached updated patch.

Thanks,
Amit

Attachments:

0001-Clarify-statement-trigger-behavior-with-inheritance.patchtext/x-diff; name=0001-Clarify-statement-trigger-behavior-with-inheritance.patchDownload
From 5c2c453235d5eedf857a5e7123337aae5aedd761 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 24 Apr 2017 14:55:08 +0900
Subject: [PATCH] Clarify statement trigger behavior with inheritance

---
 doc/src/sgml/trigger.sgml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 6f8416dda7..ce76a1f042 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -123,6 +123,14 @@
    </para>
 
    <para>
+    A statement that targets a parent table in a inheritance or partitioning
+    hierarchy does not cause the statement-level triggers of affected child
+    tables to be fired; only the parent table's statement-level triggers are
+    fired.  However, row-level triggers of any affected child tables will be
+    fired.
+   </para>
+
+   <para>
     If an <command>INSERT</command> contains an <literal>ON CONFLICT
     DO UPDATE</> clause, it is possible that the effects of all
     row-level <literal>BEFORE</> <command>INSERT</command> triggers
-- 
2.11.0

#322Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#321)
Re: Declarative partitioning - another take

On Sun, May 7, 2017 at 9:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I think that makes sense. Modified it to read: "A statement that targets
a parent table in a inheritance or partitioning hierarchy..." in the
attached updated patch.

LGTM. Committed.

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

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

#323Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Robert Haas (#322)
Re: Declarative partitioning - another take

On Wed, May 10, 2017 at 3:51 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, May 7, 2017 at 9:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

I think that makes sense. Modified it to read: "A statement that targets
a parent table in a inheritance or partitioning hierarchy..." in the
attached updated patch.

LGTM. Committed.

+ A statement that targets a parent table in a inheritance or partitioning

A tiny typo: s/a inheritance/an inheritance/

--
Thomas Munro
http://www.enterprisedb.com

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

#324Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#323)
Re: Declarative partitioning - another take

On Tue, May 9, 2017 at 11:54 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

+ A statement that targets a parent table in a inheritance or partitioning

A tiny typo: s/a inheritance/an inheritance/

Now he tells me.

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

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

#325Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#324)
Re: Declarative partitioning - another take

On 2017/05/10 12:59, Robert Haas wrote:

On Tue, May 9, 2017 at 11:54 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

+ A statement that targets a parent table in a inheritance or partitioning

A tiny typo: s/a inheritance/an inheritance/

Now he tells me.

Thanks both.

Regards,
Amit

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

#326Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Robert Haas (#133)
Re: Declarative partitioning - another take

On 2016/11/18 1:43, Robert Haas wrote:

On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.

Agree that it looks horrible. The problem is we don't add partition
(child table) RTEs when planning an insert on the parent and FDW
partitions can't do without some planner handling - planForeignModify()
expects a valid PlannerInfo for deparsing target lists (basically, to be
able to use planner_rt_fetch()).

If it's only needed for foreign tables, how about for v1 we just throw
an error and say errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table") for now. We
can come back and fix it later. Doing more inheritance expansion

Coming up with some new FDW API or some modification
to the existing one is probably better, but I don't really want to get
hung up on that right now.

I started working on this. I agree that the changes made in
make_modifytable would be unacceptable, but I'd vote for Amit's idea of
passing a modified PlannerInfo to PlanForeignModify so that the FDW can
do query planning for INSERT into a foreign partition in the same way as
for INSERT into a non-partition foreign table. (Though, I think we
should generate a more-valid-looking working-copy of the PlannerInfo
which has Query with the foreign partition as target.) I'm not sure
it's a good idea to add a new FDW API or modify the existing one such as
PlanForeignModify for this purpose.

Best regards,
Etsuro Fujita

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

#327Robert Haas
robertmhaas@gmail.com
In reply to: Etsuro Fujita (#326)
Re: Declarative partitioning - another take

On Wed, May 10, 2017 at 6:50 AM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:

I started working on this. I agree that the changes made in
make_modifytable would be unacceptable, but I'd vote for Amit's idea of
passing a modified PlannerInfo to PlanForeignModify so that the FDW can do
query planning for INSERT into a foreign partition in the same way as for
INSERT into a non-partition foreign table. (Though, I think we should
generate a more-valid-looking working-copy of the PlannerInfo which has
Query with the foreign partition as target.) I'm not sure it's a good idea
to add a new FDW API or modify the existing one such as PlanForeignModify
for this purpose.

Thanks for working on this, but I think it would be better to start a
new thread for this discussion.

And probably also for any other issues that come up. This thread has
gotten so long that between the original discussion and commit of the
patch and discussion of multiple follow-on commits and patches that
it's very hard to follow.

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

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

#328Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Maksim Milyutin (#295)
Re: Declarative partitioning - another take

Hi Maksim,

On 2017/04/07 19:52, Maksim Milyutin wrote:

On 07.04.2017 13:05, Etsuro Fujita wrote:

On 2016/12/09 19:46, Maksim Milyutin wrote:

I would like to work on two tasks:
- insert (and eventually update) tuple routing for foreign partition.
- the ability to create an index on the parent and have all of the
children inherit it;

There seems to be no work on the first one, so I'd like to work on that.

Yes, you can start to work on this, I'll join later as a reviewer.

Great! I added the patch to the next commitfest:

https://commitfest.postgresql.org/14/1184/

Sorry for the delay.

Best regards,
Etsuro Fujita

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